C # лучше инициализировать список, затем зациклить его, или просто инициализировать в состоянии цикла? - PullRequest
6 голосов
/ 26 января 2010

Я получаю много кода, подобного этому:

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

Обычно я объявляю объект, для которого я выполняю итерацию, и затем использую его в условии foreach, не опасаясь, что someMethodCall() произойдет для каждой итерации цикла. Это тот случай? Я бы предпочел сделать это:

foreach (string dateStr in someMethodCall()) { }

Но я хочу сделать это, только если someMethodCall() происходит только один раз, а затем его результаты кэшируются для каждой последующей итерации.

Ответы [ 5 ]

14 голосов
/ 26 января 2010

Метод будет вызываться только один раз в обоих случаях.

Первый метод имеет преимущество в удобочитаемости, так как вы можете назвать переменную и описать, что в ней, с ее именем. Это сделает код более самодокументированным и улучшит удобство сопровождения.


Чтобы процитировать авторитетный источник по этому вопросу:

C # Language Specification - 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 вычисляется только один раз в раскрытии.

8 голосов
/ 26 января 2010

foreach оценит коллекцию один раз, получит итератор и затем использует его для своей итерации.

1 голос
/ 26 января 2010

Один способ вспомнить, как это работает, рассмотреть это: итератор не будет работать, если будет продолжать вызывать ваш метод снова и снова.

Ваш метод возвращает список элементов. Если цикл будет вызывать ваш метод снова и снова, он (за исключением побочных эффектов) будет продолжать возвращать тот же список. Как цикл узнает во втором вызове, что он уже обработал первый элемент в списке?

Все, что вы можете перечислить, имеет метод GetEnumerator(), который должен возвращать тип (обычно это тип, реализующий IEnumerator, , но это не обязательно должно быть ). Возвращаемый тип должен иметь свойство Current и метод MoveNext().

Возвращаемый тип - это ваш объект перечислителя, и ваш цикл foreach содержит ссылку на этот объект перечислителя при перечислении. Он продолжает вызывать Current и MoveNext() для этого объекта перечислителя, пока MoveNext() не вернет false.

Использование foreach обычно более читабельно и удобно, но вы также можете перечислить «вручную», если хотите:

List<string> dates = someMethodCall();
IEnumerator<string> myEnumerator = dates.GetEnumerator();
while (myEnumerator.MoveNext())
{
    // do something with myEnumerator.Current
}
1 голос
/ 26 января 2010

Кроме того, я не уверен, каков ваш вариант использования, но если вас беспокоит объем кода, в некоторых случаях лямбды могут помочь очистить.

Например, если вы пишете операторы foreach для простого поиска определенных элементов списка, рассмотрите возможность использования лямбды .Where. Я обнаружил, что их использование, когда это уместно, уменьшило объем написанного мной кода и сделало его более читабельным в определенных ситуациях.

0 голосов
/ 26 января 2010

Я не лучший в чтении 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 
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...