高质量代码之闭包

本文借鉴《编写高质量的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();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值