c# dot operator overhead: What is more efficient

231 Views Asked by At

So I have a WPF application that has a base MVVM with child MVVMs. I tried googling the answer but wasn't sure of the technical terms so I will provide two examples below and perhaps someone can give me a little insight into the efficiency of the examples. I want to know if there is little difference in the overhead or significant.

Lets say I have a setup similar to the following

public class ParentViewModel
{
    public ParentViewModel()
    {
        Child = new ChildViewModel();
    }

    public ChildViewModel Child { get; set; }
}

public class ChildViewModel
{
    public ChildViewModel()
    {
        GrandChild = new GrandChildViewModel();
    }

    public GrandChildViewModel GrandChild { get; set; }
}

public class GrandChildViewModel
{
    public GrandChildViewModel()
    {
        GreatGrandChild = new GreatGrandChildViewModel();
    }

    public GreatGrandChildViewModel GreatGrandChild { get; set; }
}

public class GreatGrandChildViewModel
{
    public GreatGrandChildViewModel()
    {
         intA = 1;
         intB = 2;
         intC = 3;
    }

    public int intA { get; set; }
    public int intB { get; set; }
    public int intC { get; set; }
}

And the following two examples of usage is where I want the insight.

Example 1:

public Main()
{
     var parent = new ParentViewModel();

     Console.WriteLine($"A: {parent.Child.GrandChild.GreatGrandChild.intA}" +
                       $"B: {parent.Child.GrandChild.GreatGrandChild.intB}" +
                       $"C: {parent.Child.GrandChild.GreatGrandChild.intC}");
}

Example 2:

public Main()
{
     var greatGrandChild = new ParentViewModel().Child.GrandChild.GreatGrandChild;

     Console.WriteLine($"A: {greatGrandChild.intA}" +
                       $"B: {greatGrandChild.intB}" +
                       $"C: {greatGrandChild.intC}");
}

Which one is more efficient? I'm asking because I would think Example 2 would be more efficient as it goes down to the lowest level once and then accesses intA, intB, and intC. Does this matter? Is the difference in performance significant?

3

There are 3 best solutions below

1
Colin On BEST ANSWER

You will notice absolutely no optimization between the two. In fact, I suspect the compiler would optimize both types of statement into the same IL.

The latter example is more readable, however, so I'd opt for that approach.

0
Rufus L On

I would recommend getting the smallest object that you need.

The performance difference will be negligible in the example you gave, but if there happens to be a lot more data in the parent/grandparent/greatgrandparent objects, and if you are passing this object around (especially over the network), then it could make a difference. Imagine passing someone's entire family tree object to some web service that really only needs the person's name.

But it also shows your intent by grabbing the smallest object you need. Intentional programming is often easier to read and maintain, and allows you to find bugs more easily.

3
Robert Petz On

While the initial thought would be that the compiler would optimize to the same IL, that apparently isn't true

While I haven't checked the IL, a quick and dirty stopwatch test shows that the grandchild route is much faster

Using the view models from the question, this would be the results:

var parent = new ParentViewModel();
var greatGrandChild = new ParentViewModel().Child.GrandChild.GreatGrandChild;
var watch = new Stopwatch();

var a = parent.Child.GrandChild.GreatGrandChild.intA;
var b = parent.Child.GrandChild.GreatGrandChild.intB;
var c = parent.Child.GrandChild.GreatGrandChild.intC;

var bothTotal = 0L;
var longTotal = 0L;
var shortTotal = 0L;

watch.Start();
a = parent.Child.GrandChild.GreatGrandChild.intA;
b = parent.Child.GrandChild.GreatGrandChild.intB;
c = parent.Child.GrandChild.GreatGrandChild.intC;
a = greatGrandChild.intA;
b = greatGrandChild.intB;
c = greatGrandChild.intC;
watch.Stop();
bothTotal += watch.ElapsedTicks;
watch.Reset();          
Console.WriteLine("Longhand and Shorthand: " + bothTotal);

watch.Start();
a = parent.Child.GrandChild.GreatGrandChild.intA;
b = parent.Child.GrandChild.GreatGrandChild.intB;
c = parent.Child.GrandChild.GreatGrandChild.intC;
a = parent.Child.GrandChild.GreatGrandChild.intA;
b = parent.Child.GrandChild.GreatGrandChild.intB;
c = parent.Child.GrandChild.GreatGrandChild.intC;
watch.Stop();
longTotal += watch.ElapsedTicks;
watch.Reset();          
Console.WriteLine("Longhand Only: " + longTotal);

watch.Start();
a = greatGrandChild.intA;
b = greatGrandChild.intB;
c = greatGrandChild.intC;
a = greatGrandChild.intA;
b = greatGrandChild.intB;
c = greatGrandChild.intC;
watch.Stop();
shortTotal += watch.ElapsedTicks;
watch.Reset();          
Console.WriteLine("Shorthand Only: " + shortTotal);

These were my typical results:

Longhand and Shorthand: 22
Longhand Only: 3
Shorthand Only: 2

This is obviously not the most granular test and there are definitely things about it that could be argued lean this towards confirmation bias - but on the surface it does appear that the 'shorthand' route is more optimal and that further testing is merited

UPDATE

I went through and checked out the IL that is generated and, well, the results are pretty clear:

Longhand:

// Setup the object:
newobj     instance void ParentViewModel::.ctor()

// Actually call the members:
callvirt   instance class ChildViewModel ParentViewModel::get_Child()
callvirt   instance class GrandChildViewModel ChildViewModel::get_GrandChild()
callvirt   instance class GreatGrandChildViewModel 
GrandChildViewModel::get_GreatGrandChild()
callvirt   instance int32 GreatGrandChildViewModel::get_intA()
callvirt   instance class ChildViewModel ParentViewModel::get_Child()
callvirt   instance class GrandChildViewModel ChildViewModel::get_GrandChild()
callvirt   instance class GreatGrandChildViewModel 
GrandChildViewModel::get_GreatGrandChild()
callvirt   instance int32 GreatGrandChildViewModel::get_intB()
callvirt   instance class ChildViewModel ParentViewModel::get_Child()
callvirt   instance class GrandChildViewModel ChildViewModel::get_GrandChild()
callvirt   instance class GreatGrandChildViewModel 
GrandChildViewModel::get_GreatGrandChild()
callvirt   instance int32 GreatGrandChildViewModel::get_intC()

Shorthand:

// Setup the object
newobj     instance void ParentViewModel::.ctor()
call       instance class ChildViewModel ParentViewModel::get_Child()
callvirt   instance class GrandChildViewModel ChildViewModel::get_GrandChild()
callvirt   instance class GreatGrandChildViewModel GrandChildViewModel::get_GreatGrandChild()

// Actually call the members:
callvirt   instance int32 GreatGrandChildViewModel::get_intA()
callvirt   instance int32 GreatGrandChildViewModel::get_intB()
callvirt   instance int32 GreatGrandChildViewModel::get_intC()