Search code examples
c#.netvariablesoptimizationinstantiation

Is instantiating a type with initial values cheaper than two separate statements?


If I do:

string x = "Whatever";

Is that different in ANY way (other than the obvious syntax) to having it split in two lines?

string x;
x = "Whatever";

Is the first way just pure sugar?

I ask because I had a loop:

foreach(string s in MyListOfStrings)
{
     string x = Method(s);
}

And wondered if and why this was better / faster / cleaner:

string x;
foreach(string s in MyListOfStrings)
{
     x = Method(s);
}

I'm sure this has already been asked and pored over so if anyone can just point me to a good article / doc that'd be appreciated.


Solution

  • Writing that:

    static void Test()
    {
    
      // Simple initialization
    
      string s1 = "Whatever";
    
      string s2;
      s2 = "Whatever";
    
      // Loop optimization
    
      var list = new List<string>();
    
      foreach ( string s in list )
      {
        string s3 = Method(s);
      }
    
      string s4;
      foreach ( string s in list )
      {
        s4 = Method(s);
      }
    
    }
    
    static string Method(string s)
    {
      return s + s;
    }
    

    Results in this IL release code (ILSPy):

    .method private hidebysig static 
      void Test () cil managed 
    {
      // Method begins at RVA 0x34f4
      // Code size 133 (0x85)
      .maxstack 1
      .locals init (
        [0] string s1,
        [1] string s2,
        [2] class [mscorlib]System.Collections.Generic.List`1<string> list,
        [3] string s4,
        [4] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<string>,
        [5] string s,
        [6] string s3,
        [7] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<string>,
        [8] string s
      )
    
      // (no C# code)
      IL_0000: nop
      // string text1 = "Whatever";
      IL_0001: ldstr "Whatever"
      IL_0006: stloc.0
      // string text2 = "Whatever";
      IL_0007: ldstr "Whatever"
      IL_000c: stloc.1
      // List<string> list = new List<string>();
      IL_000d: newobj instance void class [mscorlib]System.Collections.Generic.List`1<string>::.ctor()
      IL_0012: stloc.2
      // (no C# code)
      IL_0013: nop
      // foreach (string item in list)
      IL_0014: ldloc.2
      IL_0015: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<string>::GetEnumerator()
      // (no C# code)
      IL_001a: stloc.s 4
      .try
      {
        IL_001c: br.s IL_0032
        // loop start (head: IL_0032)
          // foreach (string item in list)
          IL_001e: ldloca.s 4
          IL_0020: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<string>::get_Current()
          // (no C# code)
          IL_0025: stloc.s 5
          IL_0027: nop
          // string text3 = Method(item);
          IL_0028: ldloc.s 5
          IL_002a: call string ConsoleApp.Program::Method(string)
          IL_002f: stloc.s 6
          // (no C# code)
          IL_0031: nop
    
          // foreach (string item in list)
          IL_0032: ldloca.s 4
          IL_0034: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<string>::MoveNext()
          // (no C# code)
          IL_0039: brtrue.s IL_001e
        // end loop
    
        IL_003b: leave.s IL_004c
      } // end .try
      finally
      {
        IL_003d: ldloca.s 4
        IL_003f: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<string>
        IL_0045: callvirt instance void [mscorlib]System.IDisposable::Dispose()
        IL_004a: nop
        IL_004b: endfinally
      } // end handler
    
      IL_004c: nop
      // foreach (string item2 in list)
      IL_004d: ldloc.2
      IL_004e: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<string>::GetEnumerator()
      // (no C# code)
      IL_0053: stloc.s 7
      .try
      {
        IL_0055: br.s IL_006a
        // loop start (head: IL_006a)
          // foreach (string item2 in list)
          IL_0057: ldloca.s 7
          IL_0059: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<string>::get_Current()
          // (no C# code)
          IL_005e: stloc.s 8
          IL_0060: nop
          // string text4 = Method(item2);
          IL_0061: ldloc.s 8
          IL_0063: call string ConsoleApp.Program::Method(string)
          IL_0068: stloc.3
          // (no C# code)
          IL_0069: nop
    
          // foreach (string item2 in list)
          IL_006a: ldloca.s 7
          IL_006c: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<string>::MoveNext()
          // (no C# code)
          IL_0071: brtrue.s IL_0057
        // end loop
    
        // }
        IL_0073: leave.s IL_0084
      } // end .try
      finally
      {
        // (no C# code)
        IL_0075: ldloca.s 7
        IL_0077: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<string>
        IL_007d: callvirt instance void [mscorlib]System.IDisposable::Dispose()
        IL_0082: nop
        IL_0083: endfinally
      } // end handler
    
      IL_0084: ret
    } // end of method Program::Test
    

    As we can see, there is no difference at all : the code is exactly the same, without considering the NOPs (memory alignment instruction to optimize the CPU thing whose name I forgot that executes the instructions).

    For the first thing, I'm ok.

    But where I'm disapointed is that for the loop optimization as in C++ I had learned 25 years ago to do that even with compiler optimizations on, that is useless in C# (I had never checked this before).