Gle*_*den 19 c# nullable equals-operator commutativity
以下两个C#函数的区别仅在于将参数的左/右顺序交换为equals运算符==.(的类型IsInitialized是bool).使用C#7.1和.NET 4.7.
static void A(ISupportInitialize x)
{
if ((x as ISupportInitializeNotification)?.IsInitialized == true)
throw null;
}
Run Code Online (Sandbox Code Playgroud)
static void B(ISupportInitialize x)
{
if (true == (x as ISupportInitializeNotification)?.IsInitialized)
throw null;
}
Run Code Online (Sandbox Code Playgroud)
但是第二个的IL代码似乎要复杂得多.例如,B是:
newobj和initobj;[0] bool flag
nop
ldarg.0
isinst [System]ISupportInitializeNotification
dup
brtrue.s L_000e
pop
ldc.i4.0
br.s L_0013
L_000e: callvirt instance bool [System]ISupportInitializeNotification::get_IsInitialized()
L_0013: stloc.0
ldloc.0
brfalse.s L_0019
ldnull
throw
L_0019: ret
Run Code Online (Sandbox Code Playgroud)
[0] bool flag,
[1] bool flag2,
[2] valuetype [mscorlib]Nullable`1<bool> nullable,
[3] valuetype [mscorlib]Nullable`1<bool> nullable2
nop
ldc.i4.1
stloc.1
ldarg.0
isinst [System]ISupportInitializeNotification
dup
brtrue.s L_0018
pop
ldloca.s nullable2
initobj [mscorlib]Nullable`1<bool>
ldloc.3
br.s L_0022
L_0018: callvirt instance bool [System]ISupportInitializeNotification::get_IsInitialized()
newobj instance void [mscorlib]Nullable`1<bool>::.ctor(!0)
L_0022: stloc.2
ldloc.1
ldloca.s nullable
call instance !0 [mscorlib]Nullable`1<bool>::GetValueOrDefault()
beq.s L_0030
ldc.i4.0
br.s L_0037
L_0030: ldloca.s nullable
call instance bool [mscorlib]Nullable`1<bool>::get_HasValue()
L_0037: stloc.0
ldloc.0
brfalse.s L_003d
ldnull
throw
L_003d: ret
Run Code Online (Sandbox Code Playgroud)
==,(在这里,引用表达对文字值的属性),你能指出描述的细节C#规范的一部分. 奖金.每个堆叠的最终JITted x86或AMD64代码如何相应?
[edit]
基于评论中的反馈的附加说明.首先,提出了第三种变体,但它给出了与A相同的IL (对于两者Debug和Release构建).然而,在音乐上,新的C#看起来比A更光滑:
static void C(ISupportInitialize x)
{
if ((x as ISupportInitializeNotification)?.IsInitialized ?? false)
throw null;
}
Run Code Online (Sandbox Code Playgroud)
这里也是Release每个函数的IL.请注意,不对称A/C与B在ReleaseIL中仍然很明显,因此最初的问题仍然存在.
ldarg.0
isinst [System]ISupportInitializeNotification
dup
brtrue.s L_000d
pop
ldc.i4.0
br.s L_0012
L_000d: callvirt instance bool [System]ISupportInitializeNotification::get_IsInitialized()
brfalse.s L_0016
ldnull
throw
L_0016: ret
Run Code Online (Sandbox Code Playgroud)
[0] valuetype [mscorlib]Nullable`1<bool> nullable,
[1] valuetype [mscorlib]Nullable`1<bool> nullable2
ldc.i4.1
ldarg.0
isinst [System]ISupportInitializeNotification
dup
brtrue.s L_0016
pop
ldloca.s nullable2
initobj [mscorlib]Nullable`1<bool>
ldloc.1
br.s L_0020
L_0016: callvirt instance bool [System]ISupportInitializeNotification::get_IsInitialized()
newobj instance void [mscorlib]Nullable`1<bool>::.ctor(!0)
L_0020: stloc.0
ldloca.s nullable
call instance !0 [mscorlib]Nullable`1<bool>::GetValueOrDefault()
beq.s L_002d
ldc.i4.0
br.s L_0034
L_002d: ldloca.s nullable
call instance bool [mscorlib]Nullable`1<bool>::get_HasValue()
L_0034: brfalse.s L_0038
ldnull
throw
L_0038: ret
Run Code Online (Sandbox Code Playgroud)
最后,提到了一个使用新的C#7语法的版本,它似乎产生了最干净的IL:
static void D(ISupportInitialize x)
{
if (x is ISupportInitializeNotification y && y.IsInitialized)
throw null;
}
Run Code Online (Sandbox Code Playgroud)
[0] class [System]ISupportInitializeNotification y
ldarg.0
isinst [System]ISupportInitializeNotification
dup
stloc.0
brfalse.s L_0014
ldloc.0
callvirt instance bool [System]ISupportInitializeNotification::get_IsInitialized()
brfalse.s L_0014
ldnull
throw
L_0014: ret
Run Code Online (Sandbox Code Playgroud)
所以我对答案很好奇,并查看了 c# 6 规范(不知道 c# 7 规范托管在哪里)。完整免责声明:我不保证我的答案是正确的,因为我没有编写 c# 规范/编译器,并且我对内部结构的理解是有限的。
但我认为答案在于重载 ==运算符的结果。最适用的重载是通过使用更好的函数成员==的规则来确定的。
从规格来看:
给定一个参数列表 A,其中包含一组参数表达式 {E1, E2, ..., En} 和两个适用的函数成员 Mp 和 Mq,其参数类型为 {P1, P2, ..., Pn} 和 {Q1, Q2, ..., Qn}, Mp 被定义为比 Mq 更好的函数成员,如果
对于每个参数,从 Ex 到 Qx 的隐式转换并不比从 Ex 到 Px 的隐式转换更好,并且对于至少一个参数,从 Ex 到 Px 的转换优于从 Ex 到 Qx 的转换。
引起我注意的是参数列表{E1, E2, .., En}。如果将 aNullable<bool>与 a进行比较bool,参数列表应该类似于{Nullable<bool> a, bool b},对于该参数列表,该Nullable<bool>.Equals(object o)方法似乎是最好的函数,因为它只需要一次从bool到 的隐式转换object隐式转换。
但是,如果您将参数列表的顺序恢复为该{bool a, Nullable<bool> b}方法Nullable<bool>.Equals(object o),则不再是最佳函数,因为现在您必须在第一个参数中从 转换为Nullable<bool>,bool然后在第二个参数中从 转换bool为。object这就是为什么对于情况A选择不同的重载,这似乎会产生更清晰的 IL 代码。
同样,这个解释满足了我自己的好奇心,并且似乎符合 c# 规范。但我还没有弄清楚如何调试编译器以查看实际发生的情况。