Я прочитал спецификацию языка C #, посвященную условным логическим операторам ||
и &&
, также известным как логические операторы короткого замыкания. Мне казалось неясным, существуют ли они для логических значений, допускающих значение NULL, то есть типа операнда 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
of не A || B
привела к исключению времени привязки, и A
было прочитано только свойство , а не B
. Почему это происходит? Как вы понимаете, мы могли бы изменить метод получения, B
чтобы он возвращал сумасшедший объект, например "Hello world"
, и xx
все равно оценивал бы true
без проблем с привязкой ...
Вычисление A && B
(для yy
) также не приводит к ошибке времени привязки. И здесь, конечно, извлекаются оба свойства. Почему это разрешено связывателем времени выполнения? Если возвращенный объект из B
изменяется на «плохой» объект (например, a string
), возникает исключение привязки.
Это правильное поведение? (Как вы можете сделать это из спецификации?)
Если вы попробуете в B
качестве первого операнда, оба B || A
и B && A
выдадут исключение связующего времени выполнения ( B | A
и B & A
работают нормально, поскольку все в норме с операторами без короткого замыкания |
и &
).
(Пробовал с компилятором C # Visual Studio 2013 и версией среды выполнения .NET 4.5.2.)
источник
Nullable<Boolean>
задействованных вообще, только логические значения в рамке, обрабатываемые какdynamic
- ваш тест с неbool?
имеет значения. (Конечно, это не полный ответ, а только его зародыш.)A || B
имеет определенный смысл, поскольку вы не хотите оценивать,B
если неA
является ложным, а это не так. На самом деле, вы никогда не узнаете тип выражения.A && B
Версия более удивительно - я посмотрю , что я могу найти в спецификации.A
isbool
и значениеB
isnull
, тогдаbool && bool?
может быть задействован оператор.&&
говорится о его разрешении, как если бы оно было&
вместо этого, и в частности, включает случай, когда оба операндаbool?
- но тогда следующий раздел, на который он ссылается, не обрабатывает случай, допускающий значение NULL. Я мог бы добавить что-то вроде ответа, более подробно описав это, но он не объяснил бы это полностью.Ответы:
Прежде всего, спасибо за указание на то, что в спецификации нет четкого описания нединамического случая с нулевым значением-булевым. Я исправлю это в будущей версии. Поведение компилятора - это предполагаемое поведение;
&&
и||
не должны работать с значениями bool, допускающими значение NULL.Однако динамическое связывание, похоже, не реализует это ограничение. Вместо этого он отдельно связывает операции компонентов:
&
/|
и?:
. Таким образом, он может запутаться, если первым операндом окажетсяtrue
илиfalse
(которые являются логическими значениями и, следовательно, разрешены в качестве первого операнда?:
), но если вы укажетеnull
в качестве первого операнда (например, если вы попробуетеB && A
в примере выше), вы сделаете получить исключение привязки времени выполнения.Если вы задумаетесь, то поймете, почему мы реализовали динамический
&&
и||
этот способ вместо одной большой динамической операции: динамические операции связываются во время выполнения после того, как их операнды оцениваются , так что привязка может быть основана на типах времени выполнения результатов. этих оценок. Но такая нетерпеливая оценка лишает смысла операторов короткого замыкания! Вместо этого сгенерированный код для dynamic&&
и||
разбивает оценку на части и будет действовать следующим образом:x
)bool
неявное преобразование черезtrue
илиfalse
операторы или (если не удается)x
как условие в?:
операцииx
как результатy
)&
или в|
зависимости от типа среды выполненияx
иy
(сбой, если невозможно)Это поведение, которое позволяет использовать определенные «недопустимые» комбинации операндов:
?:
оператор успешно обрабатывает первый операнд как логическое значение , не допускающее значения NULL , оператор&
or|
успешно обрабатывает его как логическое значение , допускающее значение NULL , и эти два никогда не координируют свои действия для проверки их согласия. .Так что это не так уж и динамично && и || работать с значениями NULL. Просто они реализованы слишком мягко по сравнению со статическим случаем. Это, вероятно, следует считать ошибкой, но мы никогда не исправим ее, поскольку это было бы критическим изменением. К тому же это вряд ли поможет ужесточить поведение.
Надеюсь, это объясняет, что происходит и почему! Это интригующая область, и я часто сбиваюсь с толку из-за последствий решений, которые мы приняли, когда реализовали динамическую. Этот вопрос был восхитительным - спасибо, что подняли его!
Мадс
источник
dynamic
в рамки, мы не можем отличитьbool?
whichHasValue
от "simple"bool
.Да, я почти уверен, что это так.
Раздел 7.12 C # Specification Version 5.0, содержит информацию относительно условных операторов
&&
и||
и как динамическое связывание относится к ним. Соответствующий раздел:Я думаю, это ключевой момент, который отвечает на ваш вопрос. Какое разрешение происходит во время выполнения? В разделе 7.12.2, Пользовательские условные логические операторы объясняются:
В обоих случаях первый операнд x будет преобразован в логическое значение с помощью операторов
false
илиtrue
. Затем вызывается соответствующий логический оператор. Имея это в виду, у нас достаточно информации, чтобы ответить на остальные ваши вопросы.Что касается
||
оператора, мы знаем, что это следуетtrue(A) ? A : |(A, B)
. Мы закорачиваем, поэтому исключение времени привязки не будет. Даже если бы этоA
было такfalse
, мы все равно не получили бы исключение привязки времени выполнения из-за указанных шагов разрешения. ЕслиA
естьfalse
, мы тогда сделать|
оператор, который может успешно обрабатывать значения NULL, в разделе 7.11.4.По схожим причинам это тоже работает.
&&
оценивается какfalse(x) ? x : &(x, y)
.A
может быть успешно преобразован в abool
, поэтому здесь нет никаких проблем. ПосколькуB
имеет значение null,&
оператор заменяется (раздел 7.3.7) с того, который принимает a,bool
на тот, который принимаетbool?
параметры, и, таким образом, исключение времени выполнения не возникает.Для обоих условных операторов, если
B
это что-то другое, кроме логического (или нулевого динамического), привязка времени выполнения завершается ошибкой, поскольку не может найти перегрузку, которая принимает в качестве параметров логические и не-логические значения. Однако это происходит только в том случае, еслиA
не выполняется первое условие для оператора (true
for||
,false
for&&
). Это происходит потому, что динамическое связывание довольно лениво. Он не будет пытаться связать логический оператор, если он неA
является ложным, и он должен пройти по этому пути, чтобы оценить логический оператор. ЕслиA
не удается удовлетворить первое условие для оператора, произойдет сбой с исключением привязки.Надеюсь, к настоящему времени вы уже знаете, почему это происходит (или я плохо объяснил). Первый шаг в разрешении этого условного оператора - взять первый операнд
B
и использовать один из операторов преобразования типа bool (false(B)
илиtrue(B)
) перед обработкой логической операции. КонечноB
, бытиеnull
не может быть преобразовано вtrue
илиfalse
, поэтому возникает исключение привязки времени выполнения.источник
dynamic
привязка происходит во время выполнения с использованием фактических типов экземпляров, а не типов времени компиляции (ваша первая цитата). Ваша вторая цитата не имеет значения, поскольку ни один тип здесь не перегружаетoperator true
иoperator false
.explicit operator
Возвращениеbool
это нечто иное , чемoperator true
иfalse
. Трудно читать спецификацию каким-либо образом, который позволяетA && B
(в моем примере), не разрешая также,a && b
гдеa
иb
являются статически типизированными логическими значениями, допускающими значение NULL, т.е.bool? a
иbool? b
, с привязкой во время компиляции. Но это запрещено.Тип 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;
источник