开发者

C# better to initialize list then loop over it, or just initialize in loop condition?

开发者 https://www.devze.com 2022-12-17 07:32 出处:网络
I end up with a lot of code like this: List<string> dates = someMethodCall(); foreach (string dateStr in dates) { }

I end up with a lot of code like this:

List<string> dates = someMethodCall();
foreach (string dateStr in dates) { }

I usually declare the object over which I'm iterating and then use it in 开发者_JAVA技巧the foreach condition out of worry that someMethodCall() would happen for each iteration of the loop. Is this the case? I would prefer to do this:

foreach (string dateStr in someMethodCall()) { }

But I only want to do that if someMethodCall() happens only once and then its results are cached for each subsequent iteration.


The method will be called only once in both cases.

The first method has a readability advantage as you can name the variable and describe what's in it with its name. It will make the code more self-documenting and improves maintainability.


To quote the authoritative source on this:

C# Language Specification - 8.8.4 The foreach statement

 foreach (V v in x) embedded-statement

is then expanded to:

{
  E e = ((C)(x)).GetEnumerator();
  try {
  V v;
      while (e.MoveNext()) {
          v = (V)(T)e.Current;
          embedded-statement
      }
  }
  finally {
      … // Dispose e
  }
}

It's clear that the expression x in the above foreach statement is evaluated only once in the expansion.


foreach will evaluate the collection once, get the iterator, and then use that for its iteration.


Also, I'm not sure what your use case is, but if you're worried about the amount of code, lambdas can help clean up in some instances.

For example, if you're writing foreach statements to simply look for particular list elements, consider using the .Where lambda. I've found that using them, when appropriate, has decreased the amount of code I've written and made it more readable in certain situations.


One way to remember how this works it to consider this: The iterator wouldn't work if it kept calling your method over and over.

Your method returns a list of items. If the loop kept calling your method over and over, it would (barring side effects) keep getting back that same list. How would the loop know, on the second call, that it had already processed the first item in the list?

Anything you can enumerate over has a GetEnumerator() method, which must return a type (usually a type implementing IEnumerator, but it doesn't have to be). The returned type must have a Current property and a MoveNext() method.

The returned type is your enumerator object, and your foreach loop holds a reference to that enumerator object as it enumerates. It keeps calling Current and MoveNext() on that enumerator object until MoveNext() returns false.

Using foreach is usually more readable and convenient, but you can also enumerate "manually" if you want to:

List<string> dates = someMethodCall();
IEnumerator<string> myEnumerator = dates.GetEnumerator();
while (myEnumerator.MoveNext())
{
    // do something with myEnumerator.Current
}


I'm not the best at reading MSIL, but I did some testing and it seems to agree with what everyone is saying: the collection is only retrieved one time. See below for the MSIL if you're curious.

public static void ATest() {
    foreach (string s in GetSomeStrings()) {
        Console.WriteLine(s);
    }
}
public static void BTest() {
    string[] strings = GetSomeStrings();

    foreach (string s in strings) {
        Console.WriteLine(s);
    }
}
public static string[] GetSomeStrings() {
    return new string[] {
        "string1", "string2", "string3"
    };
}

ATest() MSIL:

.method public hidebysig static void ATest() cil managed
{
    .maxstack 2
    .locals init (
        [0] string s,
        [1] string[] CS$6$0000,
        [2] int32 CS$7$0001,
        [3] bool CS$4$0002)
    L_0000: nop 
    L_0001: nop 
--->L_0002: call string[] EdProgAppData_BLL.Common::GetSomeStrings()
    L_0007: stloc.1 
    L_0008: ldc.i4.0 
    L_0009: stloc.2 
    L_000a: br.s L_001d
    L_000c: ldloc.1 
    L_000d: ldloc.2 
    L_000e: ldelem.ref 
    L_000f: stloc.0 
    L_0010: nop 
    L_0011: ldloc.0 
    L_0012: call void [mscorlib]System.Console::WriteLine(string)
    L_0017: nop 
    L_0018: nop 
    L_0019: ldloc.2 
    L_001a: ldc.i4.1 
    L_001b: add 
    L_001c: stloc.2 
    L_001d: ldloc.2 
    L_001e: ldloc.1 
    L_001f: ldlen 
    L_0020: conv.i4 
    L_0021: clt 
    L_0023: stloc.3 
    L_0024: ldloc.3 
    L_0025: brtrue.s L_000c
    L_0027: ret 
}

BTest() MSIL:

.method public hidebysig static void BTest() cil managed
{
    .maxstack 2
    .locals init (
        [0] string[] strings,
        [1] string s,
        [2] string[] CS$6$0000,
        [3] int32 CS$7$0001,
        [4] bool CS$4$0002)
    L_0000: nop 
--->L_0001: call string[] EdProgAppData_BLL.Common::GetSomeStrings()
    L_0006: stloc.0 
    L_0007: nop 
    L_0008: ldloc.0 
    L_0009: stloc.2 
    L_000a: ldc.i4.0 
    L_000b: stloc.3 
    L_000c: br.s L_001f
    L_000e: ldloc.2 
    L_000f: ldloc.3 
    L_0010: ldelem.ref 
    L_0011: stloc.1 
    L_0012: nop 
    L_0013: ldloc.1 
    L_0014: call void [mscorlib]System.Console::WriteLine(string)
    L_0019: nop 
    L_001a: nop 
    L_001b: ldloc.3 
    L_001c: ldc.i4.1 
    L_001d: add 
    L_001e: stloc.3 
    L_001f: ldloc.3 
    L_0020: ldloc.2 
    L_0021: ldlen 
    L_0022: conv.i4 
    L_0023: clt 
    L_0025: stloc.s CS$4$0002
    L_0027: ldloc.s CS$4$0002
    L_0029: brtrue.s L_000e
    L_002b: ret 
}
0

精彩评论

暂无评论...
验证码 换一张
取 消