以下两个C#
函数的不同之处仅在于将参数的左/右顺序交换为等于运算符==
。 (IsInitialized
的类型为bool
)。使用 C#7.1 和 .NET 4.7 。
static void A(ISupportInitialize x)
{
if ((x as ISupportInitializeNotification)?.IsInitialized == true)
throw null;
}
static void B(ISupportInitialize x)
{
if (true == (x as ISupportInitializeNotification)?.IsInitialized)
throw null;
}
但第二个的 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
[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
==
的左侧侧出现的内容之间的差异来解释的(这里是引用表达式与文字值的属性),您是否可以指明一个部分描述细节的C#规范。x86
或AMD64
代码如何?
[编辑]
基于评论中的反馈的附加说明。首先,提出了第三个变体,但它给出了与 A 相同的IL(对于Debug
和Release
构建)。然而,在某些情况下,新的 C#看起来比 A 更光滑:
static void C(ISupportInitialize x)
{
if ((x as ISupportInitializeNotification)?.IsInitialized ?? false)
throw null;
}
这里也是每个函数的Release
IL。请注意,Release
IL的不对称性 A / C 与 B 仍然很明显,因此原始问题仍然存在。
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
[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
最后,提到了使用新的 C#7 语法的版本,它似乎产生了最干净的IL:
static void D(ISupportInitialize x)
{
if (x is ISupportInitializeNotification y && y.IsInitialized)
throw null;
}
[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
答案 0 :(得分:1)
看起来第一个操作数被转换为第二个类型以进行比较。
案例B中的多余操作涉及构建Nullable<bool>(true)
。在案例A中,为了将某些内容与true
/ false
进行比较,可以使用单个IL指令(brfalse.s
)来执行此操作。
我在C# 5.0 spec找不到具体的参考资料。 7.10关系和类型测试运算符 是指 7.3.4二元运算符重载决策 ,它又指< strong> 7.5.3重载分辨率 ,但后者非常模糊。
答案 1 :(得分:1)
所以我对答案感到好奇,并看了一下c#6规范(没有提供c#7规范托管的线索)。完全免责声明:我不保证我的答案是正确的,因为我没有编写c#规范/编译器,而且我对内部的理解是有限的。
但我认为答案在于overloadable ==
运算符的结果。 ==
的最佳适用重载是使用better function members的规则确定的。
来自规范:
给定参数列表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}
。如果将Nullable<bool>
与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>
转换为{{1在第一个参数中,然后在第二个参数中从bool
到bool
。这就是为什么在 A 的情况下选择了一个不同的重载,这似乎会导致更清晰的IL代码。
这又是一个解释,满足了我自己的好奇心,似乎符合c#规范。但我还没弄清楚如何调试编译器以查看实际发生的情况。