C# 不允许短路操作员| |和&&;是否存在可为空的布尔值?RuntimeBinder有时也这么认为

C# 不允许短路操作员| |和&&;是否存在可为空的布尔值?RuntimeBinder有时也这么认为,c#,nullable,logical-operators,short-circuiting,dynamictype,C#,Nullable,Logical Operators,Short Circuiting,Dynamictype,我阅读了关于条件逻辑运算符|和&&的C#语言规范,也称为短路逻辑运算符。对我来说,似乎不清楚是否存在可空布尔值,即操作数类型nullable(也写bool?),所以我尝试了非动态类型: bool a = true; bool? b = null; bool? xxxx = b || a; // compile-time error, || can't be applied to these types 这似乎解决了问题(我不能清楚地理解规范,但假设Visual C编译器的实现是正确的,现在我

我阅读了关于条件逻辑运算符
|
&&
的C#语言规范,也称为短路逻辑运算符。对我来说,似乎不清楚是否存在可空布尔值,即操作数类型
nullable
(也写
bool?
),所以我尝试了非动态类型:

bool a = true;
bool? b = null;
bool? xxxx = b || a;  // compile-time error, || can't be applied to these types
这似乎解决了问题(我不能清楚地理解规范,但假设Visual C编译器的实现是正确的,现在我知道了)

然而,我也想尝试使用
动态
绑定。所以我尝试了这个:

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

但是对
A | | B
xx
求值不会导致绑定时间异常,只读取了属性
A
,而不是
B
。为什么会发生这种情况?正如您所知,我们可以更改
B
getter以返回疯狂的对象,如
“Hello world”
,并且
xx
仍将计算为
true
,而不会出现绑定问题

计算
A和&B
(对于
yy
)也不会导致绑定时间错误。当然,这两个属性都会被检索到。为什么运行时绑定器允许这样做?如果从
B
返回的对象更改为“坏”对象(如
字符串
),则会发生绑定异常

这是正确的行为吗?(您如何从规范中推断出来?)

如果尝试将
B
作为第一个操作数,则
B|A
B&&A
都会给出运行时绑定异常(
B|A
B&A
工作正常,因为使用非短路运算符
&

(使用Visual Studio 2013的C#编译器和运行时版本.NET 4.5.2进行了尝试。)

这是正确的行为吗

是的,我很确定

你怎么能从说明书中推断出来呢

规范5.0版第7.12节提供了有关条件运算符
&
|
以及动态绑定如何与它们相关的信息。有关章节:

如果条件逻辑运算符的操作数具有编译时类型dynamic,则表达式是动态绑定的(§7.2.2)。在这种情况下,表达式的编译时类型是动态的,下面描述的解析将在运行时使用编译时类型为动态的操作数的运行时类型进行

我想这是回答你问题的关键点。运行时发生的解决方案是什么?第7.12.2节,用户定义的条件逻辑运算符解释:

  • 操作x&&y被评估为T.false(x)?x:T.&(x,y),其中T.false(x)是对T中声明的运算符false的调用,T.&(x,y)是对所选运算符的调用&
  • 运算x | | y被评估为T.true(x)?x:T.|(x,y),其中T.true(x)是对T中声明的运算符true的调用,而T.|(x,y)是对所选运算符|的调用
在这两种情况下,第一个操作数x将使用
false
true
运算符转换为bool。然后调用相应的逻辑运算符。考虑到这一点,我们有足够的信息来回答您的其余问题

但是对A | | B的xx求值不会导致绑定时间异常,并且只读取了属性A,而不是B。为什么会发生这种情况

对于
|
操作符,我们知道它遵循
true(A)?A:|(A,B)
。我们短路了,所以不会出现绑定时间异常。即使
A
false
,由于指定的解析步骤,我们仍然不会得到运行时绑定异常。如果
A
false
,则根据第7.11.4节,我们执行
|
运算符,该运算符可成功处理空值

计算A和B(对于yy)也不会导致绑定时间错误。当然,这两个属性都会被检索到。为什么运行时绑定器允许这样做?如果从B返回的对象更改为“坏”对象(如字符串),则会发生绑定异常

出于类似的原因,这种方法也有效<代码>&
被评估为
false(x)?x:&(x,y)
<代码>一个
可以成功地转换为一个
bool
,因此没有问题。由于
B
为null,因此
&
运算符(第7.3.7节)从接受
bool
的运算符提升为接受
bool?
参数的运算符,因此不存在运行时异常

对于这两个条件运算符,如果
B
不是bool(或null动态),则运行时绑定将失败,因为它找不到将bool和非bool作为参数的重载。但是,只有当
A
未能满足运算符的第一个条件时才会发生这种情况(
为true
表示
|
为false
表示
&
)。发生这种情况的原因是动态绑定非常懒惰。它不会尝试绑定逻辑运算符,除非
A
为false,并且它必须沿着该路径计算逻辑运算符。一旦
A
未能满足运算符的第一个条件,它将失败并出现绑定异常

如果尝试将B作为第一个操作数,则B | | A和B&&A都会给出运行时绑定异常

希望到现在为止,你已经知道为什么会发生这种情况(或者我解释得不好)
bool a = true;
bool? b = null;

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