【问题标题】:C# better to initialize list then loop over it, or just initialize in loop condition?C# 更好地初始化列表然后循环它,或者只是在循环条件下初始化?
【发布时间】:2011-01-09 06:59:56
【问题描述】:

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

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

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

foreach (string dateStr in someMethodCall()) { }

但我只想在someMethodCall() 只发生一次并且其结果为每次后续迭代缓存的情况下才这样做。

【问题讨论】:

    标签: c# optimization caching loops foreach


    【解决方案1】:

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

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


    引用权威人士的说法:

    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
      }
    }
    

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

    【讨论】:

    • 第一种方法也使调试更容易。
    • 当你命名方法调用时,谁需要var?
    • @Chaos:方法调用可能有一些参数,然后使用var 可能是有意义的,因为它可以描述这个特定返回值中的实际内容。如果描述性足够,您可以使用第二种情况。
    【解决方案2】:

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

    您的方法返回一个项目列表。如果循环一遍又一遍地调用你的方法,它会(除非有副作用)继续返回相同的列表。在第二次调用时,循环如何知道它已经处理了列表中的第一项?

    您可以枚举的任何东西都有一个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
    }
    

    【讨论】:

      【解决方案3】:

      我不是最擅长阅读 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 
      }
      

      【讨论】:

        【解决方案4】:

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

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

        【讨论】:

          【解决方案5】:

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2019-01-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-12-18
            • 1970-01-01
            相关资源
            最近更新 更多