在页156,他有一个例子,清单5.13"捕获多个委托的多个变量实例".
List<ThreadStart> list = new List<ThreadStart>();
for(int index=0; index < 5; index++;)
{
int counter = index*10;
list.Add(delegate
{
Console.WriteLine(counter);
counter++;
}
);
}
foreach(ThreadStart t in list)
{
t();
}
list[0]();
list[0]();
list[0]();
list[1]();
Run Code Online (Sandbox Code Playgroud)
在此列表之后的解释中,他说"在这种情况下,每个委托实例都捕获了一个不同的变量."
我理解这一点很好,因为我理解每次你关闭一个变量时,编译器会生成IL,将其封装在一个新的类中,专门用于允许捕获该变量(实质上使它成为一个引用类型,以便它引用的值)不会被当前执行范围的堆栈框架破坏).
但后来他谈到了如果我们index直接捕获而不是创建counter变量会发生什么- "所有代表都会共享相同的变量".
这个我不明白.是不是index在同一范围内counter?为什么编译器也不会index为每个委托创建一个新实例?
注意:我想我在输入这个问题时想出来了,但我会在这里留下问题给后人.我认为答案index实际上是在不同的范围内counter.索引基本上是在for循环中"声明"...每次都是相同的变量.
看一下为for循环生成的IL ,它证明变量是在循环外声明的(length并且i是在for循环声明中声明的变量).
.locals init (
[0] int32 length,
[1] int32 i,
[2] bool CS$4$0000
)
IL_0000: nop
IL_0001: ldc.i4.s 10
IL_0003: stloc.0
IL_0004: ldc.i4.0
IL_0005: stloc.1
IL_0006: br.s IL_001b
// loop start (head: IL_001b)
IL_0008: nop
IL_0009: ldloca.s i
IL_000b: call instance string [mscorlib]System.Int32::ToString()
IL_0010: call void [mscorlib]System.Console::WriteLine(string)
IL_0015: nop
IL_0016: nop
IL_0017: ldloc.1
IL_0018: ldc.i4.1
IL_0019: add
IL_001a: stloc.1
IL_001b: ldloc.1
IL_001c: ldloc.0
IL_001d: clt
IL_001f: stloc.2
IL_0020: ldloc.2
IL_0021: brtrue.s IL_0008
// end loop
Run Code Online (Sandbox Code Playgroud)
我认为本书可能在这个主题上做得更好的一件事就是解释编译器正在做什么,因为如果你理解编译器在新类中包含闭合的变量,那么所有这些"魔法"都是有意义的.
请纠正我可能有的任何误解或误解.此外,随时详细说明和/或添加我的解释.
听起来你已经找到了答案 - 你不会index每次循环都得到一个新实例。如果您考虑允许修改index循环内的值的方式 - 例如。如果您想跳过项目,可以增加它,在某些情况下将其设置回零,或者您喜欢的任何其他内容 - 应该清楚的是,您只有一个实例index,而不是每次迭代都有一个新实例。
另一方面,counter每次迭代都会创建一个新变量 - 如果您在该循环的底部对其进行更改,它将不会对counter下一次迭代使用的变量产生影响。
foreach循环过去常常以与循环相同的方式重用其循环变量for,这是人们常见的问题 - 请参阅C# 在 foreach 中重用变量是否有原因?
Eric Lippert解释说,他们foreach在 C# 5 中进行了更改,以便每次都获取一个新变量,并且他们将保持for原样。