Существуют ли операторы короткого замыкания || и && для нулевых булевых значений? RuntimeBinder иногда так думает


я прочитал спецификацию языка C# на условные логические операторы|| и &&, также известный как логические операторы короткого замыкания. Мне казалось неясным, существуют ли они для нулевых булевых значений, т. е. тип операнда Nullable<bool> (также написали bool?), поэтому я попробовал его с динамической типизацией:

bool a = true;
bool? b = null;
bool? xxxx = b || a;  // compile-time error, || can't be applied to these types

это, казалось, решило вопрос (я не мог четко понять спецификацию, но предполагая реализацию Компилятор Visual C# был прав, теперь я знал).

однако, я хотел попробовать с dynamic обязательными, а также. Поэтому я попробовал это вместо:

static class Program
{
  static dynamic A
  {
    get
    {
      Console.WriteLine("'A' evaluated");
      return true;
    }
  }
  static dynamic B
  {
    get
    {
      Console.WriteLine("'B' evaluated");
      return null;
    }
  }

  static void Main()
  {
    dynamic x = A | B;
    Console.WriteLine((object)x);
    dynamic y = A & B;
    Console.WriteLine((object)y);

    dynamic xx = A || B;
    Console.WriteLine((object)xx);
    dynamic yy = A && B;
    Console.WriteLine((object)yy);
  }
}

удивительный результат заключается в том, что это работает без исключения.

Ну x и y не удивительно, что их объявления приводят к получению обоих свойств, и результирующие значения, как и ожидалось,x и true и y и null.

но оценка для xx на A || B не приводит к исключению времени привязки, а только свойство A читать, а не B. Почему это происходит? Как вы можете сказать, мы могли бы изменить B getter, чтобы вернуть сумасшедший объект, как "Hello world" и xx все равно оцениваю до true без привязки-проблемы...

оценка A && B (для yy) также не приводит к ошибке привязки времени. И здесь оба свойства извлекаются, конечно. Почему это разрешено связующим во время выполнения? Если возвращаемый объект из B меняется на "плохой" объект (например,string), происходит исключение привязки.

это правильное поведение? (как вы можете сделать вывод, что из спецификации?)

если вы попытаетесь B как первый операнд, оба B || A и B && A дать runtime binder исключение (B | A и B & A работать нормально, так как все нормально с операторами без короткого замыкания | и &).

(пробовал с компилятором C# Visual Studio 2013 и версией среды выполнения .NET 4.5.2.)

3 86

3 ответа:

прежде всего, спасибо за указание на то, что спецификация не ясна в нединамическом случае nullable-bool. Я исправлю это в будущей версии. Поведение компилятора-это предполагаемое поведение; && и || не должны работать на nullable bools.

динамическая связка, похоже, не реализует это ограничение. Вместо этого он связывает операции компонента отдельно:&/| и ?:. Таким образом, он способен пробиться, если первый операнд бывает true или false (которые являются логическими значениями и поэтому разрешены в качестве первого операнда ?:), но если вы дадите null как первый операнд (например, если вы попробуете B && A в приведенном выше примере), вы получаете исключение привязки времени выполнения.

если вы подумаете об этом, вы можете понять, почему мы реализовали dynamic && и || таким образом, а не как одна большая динамическая операция: динамические операции связаны во время выполнения после своих операндов оценено, так что привязка может быть основана на типах времени выполнения результатов этих оценок. Но такая нетерпеливая оценка побеждает цель операторов короткого замыкания! Поэтому вместо этого сгенерированный код для dynamic && и || разбивает оценку на части и будет действовать следующим образом:

  • оцените левый операнд (назовем результат x)
  • попробуйте превратить его в bool через неявное преобразование, или true или false операторы (если не можете)
  • использовать x в условии ?: операция
  • в истинной ветви используйте x в результате
  • в ложной ветви,теперь вычисляет второй операнд (назовем результат y)
  • попробуйте связать & или | оператор, основанный на типе времени выполнения x и y (если не можете)
  • применить выбранный оператором

это поведение, которое пропускает определенные "незаконные" комбинации операндов:?: оператор успешно обрабатывает первый операнд как необнуляемый boolean, the & или | оператор успешно обрабатывает его как nullable boolean, и эти два никогда не координируют, чтобы проверить, что они согласны.

так что это не динамическая && и / / работа над nullables. Просто они каким-то образом реализуются это немного слишком мягко, по сравнению со статическим случаем. Это, вероятно, следует считать ошибкой, но мы никогда не исправим ее, так как это будет критическое изменение. Также это вряд ли поможет кому-то ужесточить свое поведение.

надеюсь, это объясняет, что происходит и почему! Это интригующая область, и я часто оказываюсь озадаченным последствиями решений, которые мы приняли, когда мы реализовали dynamic. Этот вопрос был очень вкусный - спасибо вверх!

Мадс

это правильное поведение?

Да, я почти уверен, что это так.

как вы можете сделать вывод, что из спецификации?

раздел 7.12 спецификации C# версии 5.0, имеет информацию относительно условных операторов && и || и как динамическая привязка относится к ним. В соответствующем разделе сайта:

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

это ключевой момент, который отвечает на ваш вопрос, я думаю. Каково разрешение, которое происходит во время выполнения? Раздел 7.12.2, пользовательские условные логические операторы объясняет:

  • операция x & & y оценивается как T. false (x) ? x: T.&(x, y), где T. false(x) - вызов оператора false, объявленного в T, а T.&(x, y) - вызов выбранного оператора &
  • операция x / / y оценивается как T. true (x) ? x: T.|(x, y), где T. true(x) - вызов оператора true, объявленного в T, и T.|(x, y) - вызов выбранного оператора |.

в обоих случаях первый операнд x будет преобразован в bool с помощью false или true операторы. Затем вызывается соответствующий логический оператор. Имея это в виду, у нас есть достаточно информации, чтобы ответить на остальные ваши вопросы.

но оценка для xx A / / B не приводит к исключению времени привязки, и только свойство a было прочитано, а не B. почему это происходит?

на || оператора, мы знаем это следует true(A) ? A : |(A, B). У нас короткое замыкание, поэтому мы не получим исключение времени привязки. Даже если A был false, мы еще не получить исключение привязки времени выполнения, из-за указанных шагов разрешения. Если A и false, а | оператор, который может успешно обрабатывать нулевые значения, в разделе 7.11.4.

оценка A & & B (для yy) также не приводит к ошибке времени привязки. И здесь оба свойства извлекаются, конечно. Почему это разрешено связующим во время выполнения? Если возвращаемый объект из B изменяется на" плохой " объект (например, строку), возникает исключение привязки.

по аналогичным причинам, это тоже работает. && оценивается как false(x) ? x : &(x, y). A может быть успешно преобразован в bool, так что нет никакой проблемы там. Потому что B имеет значение null,& оператор снимается (раздел 7.3.7) с того, который принимает bool к тому, что берет bool? параметры, и таким образом, нет исключения времени выполнения.

для условных операторов, если B является чем-то иным, чем bool (или null dynamic), привязка времени выполнения не выполняется, потому что она не может найти перегрузку, которая принимает bool и не-bool в качестве параметров. Однако это происходит только в том случае, если A не удовлетворяет первому условию для оператора (true на ||,false на &&). Причина этого заключается в том, что динамическая привязка довольно ленива. Он не будет пытаться связать логическое оператор разве что A false и он должен идти по этому пути для оценки логического оператора. Один раз A не удовлетворяет первому условию для оператора, он не будет выполнен с исключением привязки.

если вы попробуете B в качестве первого операнда, оба Б || А и б && дать исключение на этапе выполнения подшивки.

надеюсь, теперь вы уже знаете, почему это происходит (или я плохо объяснил). Первым шагом в разрешении этого условного оператора является взять первый операнд, B, и использовать один из операторов преобразования bool (false(B) или true(B)) перед обработкой логической операции. Конечно,B, будучи null не может быть преобразован либо true или false, и поэтому происходит исключение привязки времени выполнения.

тип Nullable не определяет условные логические операторы | / и &&. Я предлагаю вам следующий код:

bool a = true;
bool? b = null;

bool? xxxxOR = (b.HasValue == true) ? (b.Value || a) : a;
bool? xxxxAND = (b.HasValue == true) ? (b.Value && a) : false;