Есть ли короткое замыкание по сравнению с Math.Min или Math.Max?

При сравнении с минимумом или максимумом двух чисел/функций, замыкает ли С# короткое замыкание, если случай верен для первого и подразумевает истинность для второго? Конкретными примерами таких случаев являются

if(x < Math.Max(y, z()))

и

if(x > Math.Min(y, z()))

Поскольку Math.Max(y, z()) вернет значение не меньше y, если x ‹ y, то нет необходимости вычислять z(), что может занять некоторое время. Аналогичная ситуация с Math.Min.

Я понимаю, что оба они могут быть переписаны в соответствии с

if(x < y || x < z())

дабы накоротко, но думаю понятнее какое сравнение без переписывания. Это короткое замыкание?


person yoozer8    schedule 18.01.2012    source источник
comment
Предположим, вы вызываете if( x > XYZ(y,z())) Как компилятор может узнать результат XYZ? Макс, Мин, Среднее или что-то еще?   -  person L.B    schedule 18.01.2012
comment
@LB Отличный момент, я не рассматривал это таким образом.   -  person yoozer8    schedule 19.01.2012
comment
Кроме того, сокращенное выражение может быть не эквивалентным, в зависимости от того, как Min и Max обрабатывают NaN.   -  person dan04    schedule 19.01.2012


Ответы (5)


Как указывали другие, компилятор ничего не знает о семантике Min или Max, что позволило бы ему нарушить правило, согласно которому аргументы оцениваются до вызова метода.

Если вы хотите написать свой собственный, вы можете сделать это достаточно легко:

static bool LazyLessThan(int x, int y, Func<int> z)
{
    return x < y || x < z();
}

а затем назовите это

if (LazyLessThan(x, y, z))

or

if (LazyLessThan(x, y, ()=>z()))

Или, если на то пошло:

static bool LazyRelation<T>(T x, T y, Func<T> z, Func<T, T, bool> relation)
{
    return relation(x, y) || relation(x, z());
}
...
if (LazyRelation(x, y, ()=>z, (a,b)=> a < b))) 
person Eric Lippert    schedule 18.01.2012
comment
Последний пример был бы отличным, если бы можно было добавить аргумент по умолчанию, например: static bool LazyRelation‹T›(T x, T y, Func‹T› x, Func‹T, T, bool› ratio = (а, б)=›а ‹ б) - person zmbq; 18.01.2012
comment
+1 Отличный ответ, но давайте будем честными - все это более читаемо / ремонтопригодно, чем if(x < y || x < z()), которое предложил сам ОП? - person Yuck; 19.01.2012
comment
Я бы сказал, что он менее удобочитаем, потому что он заменяет основную идиому функцией, определение которой вы должны прочитать, чтобы понять код. - person Sean U; 19.01.2012

Нет, это не короткое замыкание, и z() всегда будет оцениваться. Если вы хотите поведение короткого замыкания, вы должны переписать, как вы это сделали.

person Matt Smith    schedule 18.01.2012
comment
Причина этого в том, что среда выполнения не может знать, каковы эффекты Max() и Min(). Вы делаете, что позволяет вам переписывать более эффективно. - person Yuck; 18.01.2012

Math.Min() и Math.Max() такие же методы, как и любые другие. Их необходимо вычислить, чтобы вернуть значение, которое будет использоваться в качестве второго аргумента при сравнении. Если вам нужно короткое замыкание, вам придется написать условие, используя оператор ||, как вы продемонстрировали.

person Adam Ralph    schedule 18.01.2012

(Нечего особенно нового добавить, но я решил поделиться результатами теста, который я провел на нем.)

Math.Max() может быть легко встроен в JIT-компилятор среды CLR, и поэтому мне было любопытно, может ли он дополнительно оптимизировать код таким образом, чтобы он сокращался.

Поэтому я набросал микробенчмарк, который оценивает два выражения по 1 000 000 раз каждое. Для z() я использовал функцию, которая вычисляет Fib(15) с помощью рекурсивного метода. Вот результаты запуска двух:

x < Math.Max(y, z()) :   8097 ms
x < y || x < z()     :     29 ms

Я предполагаю, что CLR не будет преобразовывать код каким-либо образом, чтобы предотвратить выполнение вызовов методов, потому что он не знает (и не проверяет), есть ли у процедуры какие-либо побочные эффекты.

person Sean U    schedule 18.01.2012

Нет, это не короткое замыкание, по крайней мере, на уровне компилятора С#. Math.Min или Math.Max — это два обычных вызова статических методов, и компилятор не будет оптимизировать их в этом смысле.

Порядок оценки кода будет следующим: z(), Math.Max, x > ...

Если вы действительно хотите убедиться, проверьте код IL.

person InBetween    schedule 18.01.2012