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

时间:2017-08-18 08:22:30

标签: c# nullable equals-operator commutativity

以下两个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 是:

  • 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

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

Quesions

  1. A B 之间是否存在任何功能,语义或其他重大的运行时差异? (我们只对这里的正确性感兴趣,而不是表现)
  2. 如果它们在功能上等效,那么可以暴露可观察差异的运行时条件是什么?
  3. 如果功能等价物, B 做什么(总是与 A 结果相同),以及触发的是什么它的痉挛? B 是否具有永不执行的分支?
  4. 如果差异是通过==左侧侧出现的内容之间的差异来解释的(这里是引用表达式与文字值的属性),您是否可以指明一个部分描述细节的C#规范。
  5. 是否有可靠的经验法则可用于在编码时预测臃肿的 IL ,从而避免创建它?
  6. 奖励。每个堆叠的相应最终JITted x86AMD64代码如何?

    [编辑]
    基于评论中的反馈的附加说明。首先,提出了第三个变体,但它给出了与 A 相同的IL(对于DebugRelease构建)。然而,在某些情况下,新的 C#看起来比 A 更光滑:

    static void C(ISupportInitialize x)
    {
        if ((x as ISupportInitializeNotification)?.IsInitialized ?? false)
            throw null;
    }
    

    这里也是每个函数的Release IL。请注意,Release IL的不对称性 A / C B 仍然很明显,因此原始问题仍然存在。

    释放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
    

    释放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
    

    最后,提到了使用新的 C#7 语法的版本,它似乎产生了最干净的IL:

    static void D(ISupportInitialize x)
    {
        if (x is ISupportInitializeNotification y && y.IsInitialized)
            throw null;
    }
    

    释放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 
    

2 个答案:

答案 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)方法似乎是最好的函数,因为它只是从boolobject进行一次隐式转换。

但是,如果将参数列表的顺序恢复为{bool a, Nullable<bool> b},则Nullable<bool>.Equals(object o)方法不再是最佳功能,因为现在您必须从Nullable<bool>转换为{{1在第一个参数中,然后在第二个参数中从boolbool。这就是为什么在 A 的情况下选择了一个不同的重载,这似乎会导致更清晰的IL代码。

这又是一个解释,满足了我自己的好奇心,似乎符合c#规范。但我还没弄清楚如何调试编译器以查看实际发生的情况。