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

Viewed 79

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.

1 Answers

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).

Related