C#更好地初始化列表然后循环它,或者只是在循环条件下初始化?

时间:2010-01-25 21:15:51

标签: c# optimization caching loops foreach

我最终得到了很多像这样的代码:

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

我通常声明我正在迭代的对象,然后在foreach条件下使用它,而不用担心每次迭代循环都会发生someMethodCall()。是这样的吗?我更愿意这样做:

foreach (string dateStr in someMethodCall()) { }

但是我只想在someMethodCall()只发生一次然后在每次后续迭代中缓存其结果时这样做。

5 个答案:

答案 0 :(得分:14)

在这两种情况下,该方法只会被调用一次。

第一种方法具有可读性优势,因为您可以为变量命名并用其名称描述其中的内容。它将使代码更加自我记录并提高可维护性。


引用权威来源:

  

C#语言规范 - 8.8.4 foreach语句

 foreach (V v in x) embedded-statement
     

然后扩展为:

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

很明显,上述x语句中的表达式foreach仅在扩展中计算一次。

答案 1 :(得分:8)

foreach将对集合进行一次评估,获取迭代器,然后将其用于迭代。

答案 2 :(得分:1)

另外,我不确定你的用例是什么,但如果你担心代码的数量,lambdas可以帮助清理某些情况。

例如,如果您正在编写foreach语句来查找特定的列表元素,请考虑使用.Where lambda。我发现在适当的时候使用它们减少了我编写的代码量,并使它在某些情况下更具可读性。

答案 3 :(得分:1)

记住这是如何工作的一种方法来考虑这个:如果迭代器一遍又一遍地调用你的方法,迭代器将无法工作。

您的方法返回一个项目列表。如果循环一直在反复调用你的方法,它会(禁止副作用)继续回到同一个列表。在第二次调用时,循环如何知道它已经处理了列表中的第一个项目?

您可以枚举的任何内容都有GetEnumerator()方法,该方法必须返回一个类型(通常是实现IEnumerator的类型,but it doesn't have to be)。返回的类型必须具有Current属性和MoveNext()方法。

返回的类型是您的枚举器对象,并且您的foreach循环在枚举时保存对枚举器对象的引用。它一直在该枚举器对象上调用CurrentMoveNext(),直到MoveNext()返回false。

使用foreach通常更具可读性和便利性,但如果您愿意,也可以“手动”枚举:

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

答案 4 :(得分:0)

我不是最好的阅读MSIL,但我做了一些测试,它似乎同意每个人的意见:该集合只被检索一次。如果你很好奇,请参阅下面的MSIL。

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 
}