带有Nullable <T>的'=='的参数顺序

Gle*_*den 19 c# nullable equals-operator commutativity

以下两个C#函数的区别仅在于将参数的左/右顺序交换为equals运算符==.(的类型IsInitializedbool).使用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是:

  • 36个字节(IL代码);
  • 调用附加功能,包括newobjinitobj;
  • 声明四个本地人而不是一个.

IL用于功能'A'......

[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)

IL用于功能'B'......

[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)

 

Quesions

  1. AB之间是否存在任何功能,语义或其他实质性的运行时差异?(我们只对这里的正确性感兴趣,而不是表现)
  2. 如果它们在功能上相同,那么可以暴露可观察差异的运行时条件是什么?
  3. 如果它们功能等价物,那么B在做什么(总是以与A相同的结果),以及是什么触发了它的痉挛?难道有永远无法执行分支?
  4. 如果差由上显示的内容之间的差异解释左侧面的==,(在这里,引用表达对文字值的属性),你能指出描述的细节C#规范的一部分.
  5. 是否有可靠的经验法则可用于在编码时预测膨胀的IL,从而避免创建它?

      奖金.每个堆叠的最终JITted x86AMD64代码如何相应?


[edit]
基于评论中的反馈的附加说明.首先,提出了第三种变体,但它给出了与A相同的IL (对于两者DebugRelease构建).然而,在音乐上,新的C#看起来比A更光滑:

static void C(ISupportInitialize x)
{
    if ((x as ISupportInitializeNotification)?.IsInitialized ?? false)
        throw null;
}
Run Code Online (Sandbox Code Playgroud)

这里也是Release每个函数的IL.请注意,不对称A/CBReleaseIL中仍然很明显,因此最初的问题仍然存在.

释放IL用于功能'A','C'......

        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)

释放IL用于功能'B'......

[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)

释放IL用于功能'D'......

[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)

Pet*_*ter 1

所以我对答案很好奇,并查看了 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# 规范。但我还没有弄清楚如何调试编译器以查看实际发生的情况。