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 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条件中使用它,因为担心someMethodCall()会在循环的每次迭代中发生。是这样的吗?我更愿意这样做:
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.
但是我只想这样做,如果someMethodCall()只发生一次,然后其结果被缓存用于每个后续迭代。
5 个解决方案
#1
14
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
statementforeach (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语句中的表达式x仅在扩展中计算一次。
#2
8
foreach
will evaluate the collection once, get the iterator, and then use that for its iteration.
foreach将对集合进行一次评估,获取迭代器,然后将其用于迭代。
#3
1
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.
此外,我不确定您的用例是什么,但如果您担心代码量,lambdas可以帮助清理某些情况。
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.
例如,如果您正在编写foreach语句以查找特定的列表元素,请考虑使用.Where lambda。我发现在适当的时候使用它们减少了我编写的代码量,并使其在某些情况下更具可读性。
#4
1
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.
你可以枚举的任何东西都有一个GetEnumerator()方法,它必须返回一个类型(通常是一个实现IEnumerator的类型,但它不一定是)。返回的类型必须具有Current属性和MoveNext()方法。
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.
返回的类型是您的枚举器对象,并且您的foreach循环在枚举时保存对该枚举器对象的引用。它一直在该枚举器对象上调用Current和MoveNext(),直到MoveNext()返回false。
Using foreach
is usually more readable and convenient, but you can also enumerate "manually" if you want to:
使用foreach通常更具可读性和方便性,但如果您想要,还可以“手动”枚举:
List<string> dates = someMethodCall();
IEnumerator<string> myEnumerator = dates.GetEnumerator();
while (myEnumerator.MoveNext())
{
// do something with myEnumerator.Current
}
#5
0
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.
我不是最好的阅读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:
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:
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
}
#1
14
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
statementforeach (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语句中的表达式x仅在扩展中计算一次。
#2
8
foreach
will evaluate the collection once, get the iterator, and then use that for its iteration.
foreach将对集合进行一次评估,获取迭代器,然后将其用于迭代。
#3
1
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.
此外,我不确定您的用例是什么,但如果您担心代码量,lambdas可以帮助清理某些情况。
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.
例如,如果您正在编写foreach语句以查找特定的列表元素,请考虑使用.Where lambda。我发现在适当的时候使用它们减少了我编写的代码量,并使其在某些情况下更具可读性。
#4
1
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.
你可以枚举的任何东西都有一个GetEnumerator()方法,它必须返回一个类型(通常是一个实现IEnumerator的类型,但它不一定是)。返回的类型必须具有Current属性和MoveNext()方法。
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.
返回的类型是您的枚举器对象,并且您的foreach循环在枚举时保存对该枚举器对象的引用。它一直在该枚举器对象上调用Current和MoveNext(),直到MoveNext()返回false。
Using foreach
is usually more readable and convenient, but you can also enumerate "manually" if you want to:
使用foreach通常更具可读性和方便性,但如果您想要,还可以“手动”枚举:
List<string> dates = someMethodCall();
IEnumerator<string> myEnumerator = dates.GetEnumerator();
while (myEnumerator.MoveNext())
{
// do something with myEnumerator.Current
}
#5
0
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.
我不是最好的阅读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:
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:
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
}