做短路操作|| 和&&存在可空布尔? RuntimeBinder有时会这样认为
我阅读了条件逻辑运算符 ||
上的C#语言规范 和&&
也被称为短路逻辑运算符。 对我来说,这似乎不清楚,如果这些存在可空布尔,即操作数typesNullable<bool>
(也写bool?
),所以我尝试了与非dynamictypes:
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
。
但对A || B
xx
的评价 A || B
导致没有绑定时间exception,只有属性A
被读取,而不是B
为什么会这样呢? 正如你所看到的,我们可以改变B
getter来返回一个疯狂的对象,比如"Hello world"
,并且xx
仍然会在没有绑定问题的情况下评估为true
。
评估A && B
(for yy
)也导致没有绑定时间的错误。 当然,这里的两个属性都被检索到。 为什么运行时绑定程序允许这样做? 如果从B
返回的对象更改为“坏”对象(如string
),则会发生绑定exception。
这是正确的行为? (你怎么能从规范中推断出来?)
如果你尝试B
作为第一个操作数,都是B || A
B || A
和B && A
给出运行时绑定程序exception( B | A
和B & A
正常工作,因为对于非短路运算符|
和&
,一切都正常)。
(尝试使用Visual Studio 2013的C#编译器,以及运行时版本.NET 4.5.2。)
首先,谢谢指出,在非dynamicnullable-bool的情况下,规范并不清楚。 我将在未来的版本中解决这个问题。 编译器的行为是预期的行为; &&
和||
不应该在可空的布尔上工作。
但是,dynamic联编程序似乎并没有实现这个限制。 相反,它会分别绑定组件操作: &
/ |
和?:
。 因此,如果第一个操作数恰好是true
或false
(这是布尔值,因此允许作为?:
的第一个操作数),但是如果给出null
作为第一个操作数(例如,如果尝试B && A
在上面的例子中),你会得到一个运行时绑定exception。
如果你仔细想想,你可以看到为什么我们实现了dynamic&&
和||
这种方式而不是一个大的dynamic操作:dynamic操作在操作数被评估之后在运行时绑定,以便绑定可以基于这些评估结果的运行时types。 但是这样的热切评价击败了短线操作者的目的! 相反,生成的代码用于dynamic&&
和||
将评估分解成若干部分,并按以下步骤进行:
- 评估左操作数(让我们调用结果
x
) - 尝试通过隐式转换将其转换为
bool
,或者使用true
或false
运算符(如果无法运行则失败) - 使用
x
作为?:
操作中的条件 - 在真正的分支中,使用
x
作为结果 - 在假分支中, 现在评估第二个操作数(我们称之为
y
) - 尝试绑定
&
或|
运算符基于x
和y
的运行时types(如果不能,则失败) - 应用选定的操作员
这是允许通过某些“非法”操作数组合的行为: ?:
操作符成功地将第一个操作数视为非可空布尔值, &
或|
运算符成功地将其视为可为空的布尔值,并且两者从不协调以检查他们是否同意。
所以这不是那种dynamic&&和|| 在空值工作。 只是它们碰巧是以一种有点过于宽松的方式实现的,与静态的情况相比。 这应该可能被认为是一个错误,但我们将永远不会修复它,因为这将是一个突破性的变化。 此外,它也不会帮助任何人收紧行为。
希望这解释发生了什么,为什么! 这是一个有趣的领域,我经常发现自己对我们实施dynamic决策的后果感到困惑。 这个问题很好吃 – 感谢您提出!
MADS
这是正确的行为?
是的,我很确定。
你怎么能从规范中推断出来?
C#规范版本5.0的7.12节有关于条件运算符&&
和||
以及如何dynamic绑定与他们有关。 相关部分:
如果条件逻辑运算符的操作数具有编译时typesdynamic,则该expression式是dynamic绑定的(第7.2.2节)。 在这种情况下,expression式的编译时types是dynamic的, 下面描述的分辨率将在运行时使用编译时types为dynamic 的那些操作数的运行时types来进行 。
我认为这是解决您的问题的关键。 运行时发生的分辨率是多less? 用户定义的条件逻辑操作符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 ||的xx的评价 B导致没有绑定时间exception,只有属性A被读取,而不是B.为什么会发生这种情况?
对于||
我们知道它是true(A) ? A : |(A, B)
true(A) ? A : |(A, B)
。 我们短路,所以我们不会得到一个绑定时间exception。 即使A
为false
,由于指定的分辨率步骤,我们仍然不会得到运行时绑定exception。 如果A
是false
,那么我们做|
运算符,它可以成功处理空值,根据7.11.4节。
评估A && B(for yy)也导致没有绑定时间的错误。 当然,这里两个属性都被检索到。 为什么运行时绑定程序允许这样做? 如果从B返回的对象更改为“坏”对象(如string),则会发生绑定exception。
由于类似的原因,这一个也适用。 &&
被评估为false(x) ? x : &(x, y)
false(x) ? x : &(x, y)
。 A
可以成功地转换为一个bool
,所以这里没有问题。 因为B
是空的,所以&
运算符被取消(7.3.7节)从一个bool
到一个bool
的一个bool?
参数,因此没有运行时exception。
对于这两个条件运算符,如果B
不是bool(或dynamic空值),则运行时绑定将失败,因为它无法find将bool和非bool作为参数的重载。 然而,只有当A
不能满足操作符的第一个条件时,才会发生这种情况(对||
为true
,对&&
为false
)。 发生这种情况的原因是因为dynamic绑定非常懒惰。 它不会尝试绑定逻辑运算符,除非A
是假的,并且必须沿着该path去评估逻辑运算符。 一旦A
不能满足运营商的第一个条件,它将会失败并产生绑定exception。
如果你尝试B作为第一个操作数,都是B || A和B && A给运行库绑定例外。
希望到现在为止,你已经知道为什么会发生这种情况(或者我做了一个糟糕的解释)。 解决此条件运算符的第一步是取第一个操作数B
,并在处理逻辑操作之前使用其中一个布尔转换运算符( false(B)
或true(B)
)。 当然, B
为null
不能转换为true
或false
,所以发生运行时绑定exception。
Nullabletypes不定义条件逻辑运算符|| 和&&。 我build议你下面的代码:
bool a = true; bool? b = null; bool? xxxxOR = (b.HasValue == true) ? (b.Value || a) : a; bool? xxxxAND = (b.HasValue == true) ? (b.Value && a) : false;