简体   繁体   中英

In VB.NET, C#, etc., does the one-dot rule automatically get optimized into the code?

In VB.NET, C#, etc., something like this wouldn't get optimized, would it?

'Bad
a.B.C.DoSomething1()
a.B.C.DoSomething2()
a.B.C.X = 5
DoSomething3(a.B.C.D)

'Good
Dim cachedReference As ClassOfC = a.B.C
cachedReference.DoSomething1()
cachedReference.DoSomething2()
cachedReference.X = 5
DoSomething3(cachedReference.D)

In ECMA-type languages at least, this is a good habit to be in to minimize how many times it goes to a , then goes to its B field/property, then finally goes to its C field/property. I would think that is normally the rule of thumb in any typical object-oriented or procedural language, except where there's at least a pretty solid expectation that it's just going to get optimized like that by the compiler/jit/etc. anyway. How is this handled in typical .NET, particularly for VB.NET and C#?

This, for instance, doesn't seem to mention the one-dot rule, at least pertaining to either of those two languages: https://msdn.microsoft.com/en-us/library/ms973839.aspx . On the other hand, I would be very surprised if it did this in general for properties, since those are effectively methods. It would seem to make more sense if it did this if and only if they were fields are possibly even completely trivial properties.

Okay, I went ahead and tried it out for curiosity's sake.

Given the following classes:

public class Foo
{
    public Bar Bar { get; set;}
}

public class Bar
{
    public Baz Baz { get; set;}
}

public class Baz
{
    public string One { get; set; } = string.Empty;
    public string Two { get; set; } = string.Empty;
    public bool BothPopulated() => !(string.IsNullOrWhiteSpace(One) || string.IsNullOrWhiteSpace(Two));
}

public class FooFactory
{
    public static Foo Create() => new Foo { Bar = new Bar { Baz = new Baz { One = "Hello", Two = "World" } } };
}

And the following method:

void Main()
{
    var foo = FooFactory.Create();
    var cached = foo.Bar.Baz;
    Console.WriteLine(cached.One);
    Console.WriteLine(cached.Two);
    Console.WriteLine(cached.BothPopulated());

    var fooTwo = FooFactory.Create();
    Console.WriteLine(fooTwo.Bar.Baz.One);
    Console.WriteLine(fooTwo.Bar.Baz.Two);
    Console.WriteLine(fooTwo.Bar.Baz.BothPopulated());
}

LinqPad reports the IL emitted for main in release mode as

IL_0000:  call        UserQuery+FooFactory.Create
IL_0005:  callvirt    UserQuery+Foo.get_Bar
IL_000A:  callvirt    UserQuery+Bar.get_Baz
IL_000F:  dup         
IL_0010:  callvirt    UserQuery+Baz.get_One
IL_0015:  call        System.Console.WriteLine
IL_001A:  dup         
IL_001B:  callvirt    UserQuery+Baz.get_Two
IL_0020:  call        System.Console.WriteLine
IL_0025:  callvirt    UserQuery+Baz.BothPopulated
IL_002A:  call        System.Console.WriteLine // <- End of cached portion
IL_002F:  call        UserQuery+FooFactory.Create
IL_0034:  dup         
IL_0035:  callvirt    UserQuery+Foo.get_Bar
IL_003A:  callvirt    UserQuery+Bar.get_Baz
IL_003F:  callvirt    UserQuery+Baz.get_One
IL_0044:  call        System.Console.WriteLine 
IL_0049:  dup         
IL_004A:  callvirt    UserQuery+Foo.get_Bar
IL_004F:  callvirt    UserQuery+Bar.get_Baz
IL_0054:  callvirt    UserQuery+Baz.get_Two
IL_0059:  call        System.Console.WriteLine
IL_005E:  callvirt    UserQuery+Foo.get_Bar
IL_0063:  callvirt    UserQuery+Bar.get_Baz
IL_0068:  callvirt    UserQuery+Baz.BothPopulated
IL_006D:  call        System.Console.WriteLine
IL_0072:  ret 

So it looks like you do save some callvirts by not drilling through the properties each time. Whether this has any measurable impact on the JIT at run-time is not something I can answer, but it does appear to leave a smaller IL footprint.

I suspect it has basically zero impact on run-time performance.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM