Логический порядок C# и поведение компилятора

В C# (и не стесняйтесь отвечать для других языков), в каком порядке среда выполнения оценивает логический оператор?

Пример:

DataTable myDt = new DataTable();
if (myDt != null && myDt.Rows.Count > 0)
{
    //do some stuff with myDt
}

Какой оператор сначала оценивает среда выполнения -

myDt != null

или:

myDt.Rows.Count > 0

?

Был ли момент, когда компилятор когда-либо оценивал оператор в обратном направлении? Возможно, когда задействован оператор «ИЛИ»?


& известен как логический побитовый оператор и всегда будет оценивать все подвыражения

Каков хороший пример использования побитового оператора вместо «короткозамкнутого логического»?

Ответов (18)

Решение

C#: слева направо, и обработка останавливается, если обнаружено несоответствие (оценивается как ложное).

Левый, затем останавливается, если он равен нулю.

Изменить: в vb.net он оценит оба и, возможно, выдаст ошибку, если вы не используете AndAlso

Я где-то слышал, что компиляторы работают в обратном порядке, но я не уверен, насколько это верно.

Вы можете использовать и когда вы специально хотите , чтобы оценить все подвыражения, скорее всего , потому что они имеют побочные эффекты , которые вы хотите, даже если конечный результат будет ложным , и , таким образом , не выполнить свое то часть вашего , если -statement.

Обратите внимание, что & и | работает как для побитовых масок, так и для логических значений, а не только для побитовых операций. Они называются поразрядными, но в C# они определены как для целых, так и для логических типов данных.

@csmba :

В последующем спросили, почему и когда кто-то будет использовать And вместо AndAlso (или & вместо &&): Вот пример:

if ( x.init() And y.init()) then
   x.process(y)
end 
y.doDance()

В этом случае я хочу инициировать как X, так и Y. Y должен быть инициализирован, чтобы y.DoDance мог выполняться. Однако в функции init () я делаю еще кое-что, например, проверяю, что сокет открыт, и только если это сработает, для обоих я должен пойти дальше и выполнить x.process (y).

Я считаю, что это довольно сбивает с толку. Хотя ваш пример работает, это не типичный случай для использования And (и я бы, вероятно, написал это по-другому, чтобы было понятнее). And ( & в большинстве других языков) на самом деле является побитовой операцией и. Вы могли бы использовать его для вычисления битовых операций, например, удаления бита флага или флагов маскирования и тестирования:

Dim x As Formatting = Formatting.Bold Or Formatting.Italic
If (x And Formatting.Italic) = Formatting.Italic Then
    MsgBox("The text will be set in italic.")
End If

Язык программирования D выполняет оценку слева направо с коротким замыканием и не допускает перегрузки &&и '||' операторы.

vb.net

if( x isNot Nothing AndAlso x.go()) then
  1. Оценка выполняется слева направо
  2. Оператор AndAlso гарантирует, что только если левая сторона была ИСТИНА, правая сторона будет оценена (очень важно, так как если x ничего, x.go выйдет из строя)

Вы можете использовать And вместо AndAlso в vb. в этом случае левая сторона также оценивается первой, но правая сторона оценивается независимо от результата.

Лучшая практика: всегда используйте AndAlso, если у вас нет веской причины, почему бы не делать этого.


В последующем спросили, почему и когда кто-то будет использовать And вместо AndAlso (или & вместо &&): Вот пример:

if ( x.init() And y.init()) then
   x.process(y)
end 
y.doDance()

В этом случае я хочу инициировать как X, так и Y. Y должен быть инициализирован, чтобы y.DoDance мог выполняться. Однако в функции init () я делаю еще кое-что, например, проверяю, что сокет открыт, и только если это сработает, для обоих я должен пойти дальше и выполнить x.process (y).

Опять же, это, вероятно, не нужно и не элегантно в 99% случаев, поэтому я сказал, что по умолчанию следует использовать AndAlso .

Нет, по крайней мере, компилятор C# не работает в обратном направлении (в && или ||). Это слева направо.

В некоторых языках есть интересные ситуации, когда выражения выполняются в другом порядке. Я имею в виду именно Ruby, но уверен, что они позаимствовали его из других источников (вероятно, Perl).

Выражения в логике останутся слева направо, но, например:

puts message unless message.nil?

Вышеупомянутое будет оценивать "message.nil?" сначала, а затем, если он оценивается как ложный (кроме случаев, когда он выполняется, когда условие ложно, а не истинно), будет выполнено «помещает сообщение», которое выводит содержимое переменной сообщения на экран.

Иногда это интересный способ структурировать код ... Мне лично нравится использовать его для очень коротких 1 лайнеров, как показано выше.

Редактировать:

Чтобы было немного понятнее, приведенное выше то же самое, что:

unless message.nil?
  puts message
end

Под скромностью подразумевается перегрузка оператора. в заявлении:

if( A && B){
    // do something
}

Сначала оценивается A, если он оценивается как ложь, B никогда не оценивается. То же самое относится к

if(A || B){
    //do something
}

Сначала оценивается A, если оно истинно, B никогда не оценивается.

Эта концепция перегрузки применима (я думаю) ко всем языкам стиля C, а также ко многим другим.

Обратите внимание, что существует разница между && и & относительно того, какая часть вашего выражения оценивается.

&& известен как короткозамкнутое логическое И, и, как отметили другие здесь, остановится раньше, если результат может быть определен до того, как будут оценены все подвыражения.

& известен как логический побитовый оператор и всегда вычисляет все подвыражения.

Как таковой:

if (a() && b())

Будет вызывать только b, если a возвращает true .

однако это:

if (a() & b())

Всегда будет вызывать как a, так и b , даже если результат вызова a ложный и, следовательно, заведомо ложный, независимо от результата вызова b .

Такая же разница существует для || и | операторы.

ZombieSheep беспощаден. Единственный "подводный камень", который может подождать, - это то, что это верно только в том случае, если вы используете оператор &&. При использовании оператора & оба выражения будут оцениваться каждый раз, независимо от того, оценивается ли одно или оба как ложные.

if (amHungry & whiteCastleIsNearby)
{
   // The code will check if White Castle is nearby
   // even when I am not hungry
}

if (amHungry && whiteCastleIsNearby)
{
   // The code will only check if White Castle is nearby
   // when I am hungry
}

Когда все в порядке, они выполняются слева направо.

Когда вещи вложены, они выполняются от внутреннего к внешнему. Это может показаться запутанным, поскольку обычно то, что «самое внутреннее» находится в правой части строки, поэтому кажется, что оно идет в обратном направлении ...

Например

a = Foo( 5, GetSummary( "Orion", GetAddress("Orion") ) );

Бывает так:

  • Звоните GetAddressс буквальным"Orion"
  • Вызов GetSummaryс буквальным "Orion"и результатомGetAddress
  • Вызов Fooс буквальным 5и результатомGetSummary
  • Присвойте это значение a

Каков хороший пример использования побитового оператора вместо «короткозамкнутого логического»?

Предположим, у вас есть флаги, скажем, для атрибутов файлов. Предположим, вы определили READ как 4, WRITE как 2 и EXEC как 1. В двоичном формате это:

READ  0100  
WRITE 0010  
EXEC  0001

Каждый флаг имеет один набор битов, и каждый из них уникален. Побитовые операторы позволяют комбинировать эти флаги:

flags = READ & EXEC; // value of flags is 0101

Я понимаю, что на этот вопрос уже был дан ответ, но я хотел бы добавить еще немного информации, относящейся к этой теме.

В языках, таких как C++, где вы можете фактически перегрузить поведение && и || операторам настоятельно рекомендуется этого не делать . Это связано с тем, что, когда вы перегружаете это поведение, вы в конечном итоге заставляете оценивать обе стороны операции. Это делает две вещи:

  1. Это нарушает ленивый механизм оценки, потому что перегрузка - это функция, которую нужно вызвать, и, следовательно, оба параметра оцениваются перед вызовом функции.
  2. Порядок оценки указанных параметров не гарантируется и может зависеть от компилятора. Следовательно, объекты не будут вести себя так, как в примерах, перечисленных в вопросе / предыдущих ответах.

Для получения дополнительной информации прочитайте книгу Скотта Мейерса « Более эффективный C++» . Ваше здоровье!

@shsteimer

Под скромностью подразумевается перегрузка оператора. в заявлении: ... A оценивается первым, если он оценивается как false, B никогда не оценивается. То же самое относится к

Это не перегрузка оператора. Перегрузка оператора - это термин, который позволяет вам определять настраиваемое поведение для операторов, например *, +, = и т. Д.

Это позволит вам написать свой собственный класс «Журнал», а затем выполнить

a = new Log(); // Log class overloads the + operator
a + "some string"; // Call the overloaded method - otherwise this wouldn't work because you can't normally add strings to objects.

Делая это

a() || b() // be never runs if a is true

на самом деле называется оценкой короткого замыкания

Мне нравятся ответы Ориона. Я добавлю две вещи:

  1. Слева направо по-прежнему применяется первым
  2. От внутреннего к внешнему, чтобы убедиться, что все аргументы разрешены перед вызовом функции

Допустим, у нас есть следующий пример:

a = Foo(5, GetSummary("Orion", GetAddress("Orion")),
           GetSummary("Chris", GetAddress("Chris")));

Вот порядок исполнения:

  1. GetAddress("Orion")
  2. GetSummary("Orion", ...)
  3. GetAddress("Chris")
  4. GetSummary("Chris", ...)
  5. Foo(...)
  6. Назначается a

Я не могу говорить о юридических требованиях C# (хотя я тестировал аналогичный пример с использованием Mono перед написанием этого сообщения), но этот порядок гарантирован в Java.

И просто для полноты (поскольку это также независимый от языка поток), существуют такие языки, как C и C++, где порядок не гарантируется, если нет точки последовательности. Литература: 1 , 2 . Однако при ответе на вопрос потока && и || являются точками последовательности в C++ (если не перегружены; также см. Отличный ответ OJ). Итак, несколько примеров:

  • foo() && bar()
  • foo() & bar()

В этом && случае foo() гарантированно запускается раньше bar() (если последний запускается вообще), т.к. && является точкой последовательности. В этом & случае такая гарантия не предоставляется (в C и C++) и действительно bar() может выполняться раньше foo() или наоборот.

«C#: слева направо, и обработка останавливается, если найдено совпадение (истинно)».

Зомби-овца ошибается, недостаточно репутации, чтобы проголосовать против.

Вопрос в операторе &&, а не в || оператор.

В случае && оценка остановится, если будет найдено ЛОЖЬ.

В случае || оценка останавливается, если обнаруживается ИСТИНА.