Существуют ли операторы короткого замыкания || и && для нулевых булевых значений? 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 ответа:
прежде всего, спасибо за указание на то, что спецификация не ясна в нединамическом случае 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
, и поэтому происходит исключение привязки времени выполнения.