本文借鉴《编写高质量的C#代码:改善C#程序的157个建议》,算是对自己学习的总结,也希望分享下所学知识~~
先来看一段代码:
List<Action> lists = new List<Action>();
for (int i = 0; i < 5; i++)
{
Action t = () =>
{
Console.WriteLine(i.ToString());
};
lists.Add(t);
}
foreach (var item in lists)
{
item.Invoke();
}
我们期望的结果为:0、1、2、3、4。
但是,实际结果为:5、5、5、5、5。
为什么没像预期那样执行?
首先看看生成的IL代码:
.maxstack 3
.locals init ([0] class [mscorlib]System.Collections.Generic.List`1<class [mscorlib]System.Action> lists,
[1] class BetterCode.Program/'<>c__DisplayClass0_0' 'CS$<>8__locals0',
[2] class [mscorlib]System.Action t,
[3] int32 V_3,
[4] bool V_4,
[5] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class [mscorlib]System.Action> V_5,
[6] class [mscorlib]System.Action item)
IL_0000: nop
IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`1<class [mscorlib]System.Action>::.ctor()
IL_0006: stloc.0
IL_0007: newobj instance void BetterCode.Program/'<>c__DisplayClass0_0'::.ctor()
IL_000c: stloc.1
IL_000d: ldloc.1
IL_000e: ldc.i4.0
IL_000f: stfld int32 BetterCode.Program/'<>c__DisplayClass0_0'::i
IL_0014: br.s IL_003d
IL_0016: nop
IL_0017: ldloc.1
IL_0018: ldftn instance void BetterCode.Program/'<>c__DisplayClass0_0'::'<Main>b__0'()
IL_001e: newobj instance void [mscorlib]System.Action::.ctor(object,
native int)
IL_0023: stloc.2
IL_0024: ldloc.0
IL_0025: ldloc.2
IL_0026: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<class [mscorlib]System.Action>::Add(!0)
IL_002b: nop
IL_002c: nop
IL_002d: ldloc.1
IL_002e: ldfld int32 BetterCode.Program/'<>c__DisplayClass0_0'::i
IL_0033: stloc.3
IL_0034: ldloc.1
IL_0035: ldloc.3
IL_0036: ldc.i4.1
IL_0037: add
IL_0038: stfld int32 BetterCode.Program/'<>c__DisplayClass0_0'::i
IL_003d: ldloc.1
IL_003e: ldfld int32 BetterCode.Program/'<>c__DisplayClass0_0'::i
IL_0043: ldc.i4.5
IL_0044: clt
IL_0046: stloc.s V_4
IL_0048: ldloc.s V_4
IL_004a: brtrue.s IL_0016
IL_004c: nop
IL_004d: ldloc.0
IL_004e: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<class [mscorlib]System.Action>::GetEnumerator()
IL_0053: stloc.s V_5
//...省略无用的
在 IL_0007 行,创建了一个对象 <>c__DisplayClass0_0 ,每次循环内部都会为这个类的一个实例变量 i 赋值。
这就是所谓的闭包对象。
结论:
如果匿名方法(Lambda表达式)引用了某个局部变量,编译器就会自动将该引用提升到该闭包对象中,即将 for 循环中的 i 变量修改成了闭包对象的公共变量 i 。
只有这样,代码执行后离开了原局部变量 i 的作用域,包含该闭包对象的作用域也还存在。
相当于以下代码:
class TempClass
{
public int i;
public void TempFuc()
{
Console.WriteLine(i.ToString());
}
}
List<Action> lists = new List<Action>();
TempClass tempClass = new TempClass();
for (tempClass.i = 0; tempClass.i < 5; tempClass.i++)
{
Action t = tempClass.TempFuc;
lists.Add(t);
}
foreach (var item in lists)
{
item.Invoke();
}
如果想要实现不同的效果,可以将闭包对象的产生放在 for 循环内部:
List<Action> lists = new List<Action>();
for (int i = 0; i < 5; i++)
{
int temp = i;
Action t = () =>
{
Console.WriteLine(temp.ToString());
};
lists.Add(t);
}
foreach (var item in lists)
{
item.Invoke();
}
其对应的生成代码就是:
List<Action> lists = new List<Action>();
for (int i = 0; i < 5; i++)
{
TempClass tempClass = new TempClass();
tempClass.i = i;
Action t = tempClass.TempFuc;
lists.Add(t);
}
foreach (var item in lists)
{
item.Invoke();
}