我最终得到了很多像这样的代码:
List<string> dates = someMethodCall();
foreach (string dateStr in dates) { }
我通常声明我正在迭代的对象,然后在foreach
条件下使用它,而不用担心每次迭代循环都会发生someMethodCall()
。是这样的吗?我更愿意这样做:
foreach (string dateStr in someMethodCall()) { }
但是我只想在someMethodCall()
只发生一次然后在每次后续迭代中缓存其结果时这样做。
答案 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循环在枚举时保存对枚举器对象的引用。它一直在该枚举器对象上调用Current
和MoveNext()
,直到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
}