0


I wanted to find out what more efficient (if even) in the following code
Value type

ForEach(string s in strings)
{
  string t = s;
}
// or
string t;
ForEach(string s in strings)
{
   t = s;
}

and is there a different with reference types.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
guyl
  • 2,158
  • 4
  • 32
  • 58

8 Answers8

10

The two code snippets produce exactly the same IL. Here's the C# code I used to test with:

  public string[] strings = new string[5];

  public void TestOne()
  {
     foreach (string s in strings)
     {
        string t = s;
     }
  }

  public void TestTwo()
  {
     string t;
     foreach (string s in strings)
     {
        t = s;
     }
  }

And here's the resulting IL for both methods after compiling with optimizations enabled:

.method public hidebysig instance void  TestOne() cil managed
{
  // Code size       26 (0x1a)
  .maxstack  2
  .locals init ([0] string s,
           [1] string[] CS$6$0000,
           [2] int32 CS$7$0001)
  IL_0000:  ldarg.0
  IL_0001:  ldfld      string[] strings
  IL_0006:  stloc.1
  IL_0007:  ldc.i4.0
  IL_0008:  stloc.2
  IL_0009:  br.s       IL_0013
  IL_000b:  ldloc.1
  IL_000c:  ldloc.2
  IL_000d:  ldelem.ref
  IL_000e:  stloc.0
  IL_000f:  ldloc.2
  IL_0010:  ldc.i4.1
  IL_0011:  add
  IL_0012:  stloc.2
  IL_0013:  ldloc.2
  IL_0014:  ldloc.1
  IL_0015:  ldlen
  IL_0016:  conv.i4
  IL_0017:  blt.s      IL_000b
  IL_0019:  ret
} // end of method TestOne


.method public hidebysig instance void  TestTwo() cil managed
{
  // Code size       26 (0x1a)
  .maxstack  2
  .locals init ([0] string s,
           [1] string[] CS$6$0000,
           [2] int32 CS$7$0001)
  IL_0000:  ldarg.0
  IL_0001:  ldfld      string[] strings
  IL_0006:  stloc.1
  IL_0007:  ldc.i4.0
  IL_0008:  stloc.2
  IL_0009:  br.s       IL_0013
  IL_000b:  ldloc.1
  IL_000c:  ldloc.2
  IL_000d:  ldelem.ref
  IL_000e:  stloc.0
  IL_000f:  ldloc.2
  IL_0010:  ldc.i4.1
  IL_0011:  add
  IL_0012:  stloc.2
  IL_0013:  ldloc.2
  IL_0014:  ldloc.1
  IL_0015:  ldlen
  IL_0016:  conv.i4
  IL_0017:  blt.s      IL_000b
  IL_0019:  ret
} // end of method TestTwo

As usual, the rule is the same: trust your compiler. Let it handle these optimizations for you, rather than trying to worry about it as you write the code. Just write the code that makes sense and is readable.

Most importantly, ignore all the people who argue that one of them is "theoretically" better than the other. That's pure nonsense. There's no theory that's relevant until after the code is compiled. And since it compiles down to the exact same thing, it's guaranteed to be completely identical in performance.

But if you still don't believe my urgings to trust your compiler (some of us are stubborn, I know because even I still am sometimes), at least you now know how to answer these types of questions on your own. Write some sample code that demonstrates both variations, compile it in "Release" mode, then open up the assembly in ILDASM.exe and compare the resulting IL code for yourself. Things like this are so easy to test, there's never a reason to guess.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
5

Well neither of those will compile (I guess that you want to use foreach instead of ForEach?), but if they did they should compile down to the same IL after optimizations and so there would be no difference.

Justin
  • 84,773
  • 49
  • 224
  • 367
2

You don't need to do this at all. Just use s.

Also, string in C#/BCL is a reference type.

Daniel A. White
  • 187,200
  • 47
  • 362
  • 445
  • Don't you think that the given code is demonstrative rather than explicit? – Lazarus May 04 '11 at 13:50
  • You have to take a copy if you want to modify it e.g. you can't do `s = s.Trim();` if s is the iteration variable. – Rup May 04 '11 at 13:58
  • Really? I get [error CS1656](http://msdn.microsoft.com/en-us/library/369xac69.aspx): Cannot assign to 's' because it is a 'foreach iteration variable' – Rup May 04 '11 at 14:18
1

They're both the same for memory effiency, because behind the scenes they all point to the same string. This is a semantic used by .NET/Java, and the reason why strings are immutable.

Justin Simon
  • 1,133
  • 8
  • 8
  • String interning is not the reason why strings are immutable (at least, not in .NET, I don't know anything about Java), and it's also not the reason why this code is the same in terms of memory efficiency. That's merely an implementation detail, and a fairly irrelevant one in this case. – Cody Gray - on strike May 04 '11 at 14:01
1

Theoretically speaking, the second one should be more efficient because the memory allocation is performed only once before the loop starts, while the first one should try to allocate memory at every iteration.

However i do not know if there is any optimization performed by the compiler based on how the variable is used inside and outside the loop (in the second case the variable could be used in other parts of the code, not only in the loop).

il_guru
  • 8,383
  • 2
  • 42
  • 51
  • Your speculation about memory usage is incorrect. Since strings are immutable, each time you assign a new one to an existing variable, the runtime has to allocate more memory for that string. It really doesn't do any good to reuse the same variable to declare a new one. – Cody Gray - on strike May 04 '11 at 14:00
  • Yes but theoretically in the first case you need to allocate memory for a reference at every iteration, while in the second case you create a reference once. The compiler could optimize creating the reference once and then initilizing it at every iteration. However if you assign your reference to something else (maybe a list) you have to create a new reference every time i think. However in this specfic case, this doesn't matter – il_guru May 04 '11 at 14:22
  • That's simply not how it works. You're throwing out the term "theoretically", as if it somehow justifies your theory being demonstrably false. And I don't even understand how you suppose that it works "in theory". The runtime doesn't re-use the *same* object by simply re-initializing it. Garbage collection has to happen in between, and since GC is an expensive process, it's delayed until either the system is idle or it's running so low on memory that it must attempt a collection. Either way, it's going to create new instances of the `String` class each time, consuming the same amount of memory – Cody Gray - on strike May 04 '11 at 14:25
  • I used "theoretically" because the code is optimized by the compiler, so you do not know how it is going to change your code (in this example two different code create the same IL). I do not agree on creating a new instance of string every time in both the examples, because string t = s is an initialization while t = s is just a reference assignment. In the second case it is possible that the GC does nothing on the string because it is used again outside the loop. i was just speculating about the fact that a variable could be used again in the second example if the code continue. – il_guru May 04 '11 at 15:31
1

strings are immutable reference types.

So the code

string t;
foreach(string s in strings)
{
   t = s;
}

creates a new string instance for each step in the enumerator anyway (it doesn't "reuse" the instance t).

There is no practical difference between the two code examples you posted.

fearofawhackplanet
  • 52,166
  • 53
  • 160
  • 253
0

I would bet that the compiler is intelligent enough to build them in exactly the same way. I don't think there is any difference, go on readability.

Tom Gullen
  • 61,249
  • 84
  • 283
  • 456
0

This is the same code, only the declaration is at another place. You should always try to declare the variables next to their use, using the smallest possible scope.

Note that foreach is written lowercase like all keywords in c#.

codymanix
  • 28,510
  • 21
  • 92
  • 151