call instead of callvirt in case of the new c# 6 “?” null check

前端 未结 3 1673
后悔当初
后悔当初 2021-02-19 06:17

Given the two methods:

    static void M1(Person p)
    {
        if (p != null)
        {
            var p1 = p.Name;
        }
    }

    static void M2(Perso         


        
相关标签:
3条回答
  • 2021-02-19 06:54

    The callvirt in M1 is standard C# code generation. It provides the language guarantee that an instance method can never be called with a null reference. In other words, it ensures that p != null and generates NullReferenceException if it is null. Your explicit test does not change that.

    This guarantee is pretty nice, debugging NRE gets pretty hairy if it is this that is null. Much easier to diagnose the mishap at the call-site instead, the debugger can quickly show you that it is p that is the troublemaker.

    But of course callvirt is not for free, although the cost is very low, one extra processor instruction at runtime. So if it can be substituted by call then the code will be faster by half a nanosecond, give or take. It in fact can with the elvis operator since it already ensures that the reference isn't null so the C# 6 compiler took advantage of that and generates call instead of callvirt.

    0 讨论(0)
  • 2021-02-19 07:10

    Start with the fact that callvirt is used instead of call because of the C# rule that null objects may not have methods called on them, even when .NET allows it.

    Now, in both your methods, we can show statically that p is not null, and as such using call instead of callvirt is not going to break this C# rule, and as such is a reasonable optimisation.

    While if (a != null) a.b etc. is a common idiom, it takes analysis to realise that a cannot be null at the point that b is used. Adding that analysis to the compiler would take work specing, implementing, testing, and continually testing against regression bugs introduced by other changes.

    a?.b is beyond an idiom, in that it's using an operator ?. that C# must "know" about. So C# has to have the code to turn this into a null check followed by a member access. So the compiler has to know that at the point where the member access happens, a is not null. As such the logic to "know" the use of call is safe has already been done. It's no extra analysis work to realise that call can be used.

    So the first case would require a bunch of extra work to use call and potentially introduce bugs, while the second case has to do that work anyway, so it might as well.

    0 讨论(0)
  • 2021-02-19 07:16

    I think it's clearly now,

    This is an easy and thread-safe way to check for null before you trigger an event. The reason it’s thread-safe is that the feature evaluates the left-hand side only once, and keeps it in a temporary variable. MSDN

    So it is safe to use call instruction here.

    I wrote a blog post about the differences between call and callvirt and why C# generate callvirt

    Thanks Dan Lyons for the MSDN link.

    0 讨论(0)
提交回复
热议问题