zoukankan      html  css  js  c++  java
  • 编写高质量代码改善C#程序的157个建议——建议38:小心闭包中的陷阱

    建议38:小心闭包中的陷阱

    先看一下下面的代码,设想一下输出的是什么?

            static void Main(string[] args)
            {
                List<Action> lists = new List<Action>();
                for (int i = 0; i < 5; i++)
                {
                    Action t = () =>
                    {
                        Console.WriteLine(i.ToString());
                    };
                    lists.Add(t);
                }
                foreach (Action t in lists)
                {
                    t();
                }
            }

    我们的设计意图是让匿名方法(在这里表现为Lambda表达式)接受参数 i ,并输出:

    0

    1

    2

    3

    4

    而实际上输出为:

    5

    5

    5

    5

    5

    这段代码并不像我们想象的那么简单,要完全理解运行时代码是怎么运行的,首先必须理解C#编译器为我们做了什么。

    IL代码如下:

    .method private hidebysig static void Main(string[] args) cil managed
    {
        .entrypoint
        .maxstack 3
        .locals init (
            [0] class [mscorlib]System.Collections.Generic.List`1<class [mscorlib]System.Action> lists,
            [1] class [mscorlib]System.Action t,
            [2] class [mscorlib]System.Action CS$<>9__CachedAnonymousMethodDelegate1,
            [3] class MyTest.Program/<>c__DisplayClass2 CS$<>8__locals3,
            [4] bool CS$4$0000,
            [5] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class [mscorlib]System.Action> CS$5$0001)
        L_0000: nop 
        L_0001: newobj instance void [mscorlib]System.Collections.Generic.List`1<class [mscorlib]System.Action>::.ctor()
        L_0006: stloc.0 
        L_0007: ldnull 
        L_0008: stloc.2 
        L_0009: newobj instance void MyTest.Program/<>c__DisplayClass2::.ctor()
        L_000e: stloc.3 
        L_000f: ldloc.3 
        L_0010: ldc.i4.0 
        L_0011: stfld int32 MyTest.Program/<>c__DisplayClass2::i
        L_0016: br.s L_0044
        L_0018: nop 
        L_0019: ldloc.2 
        L_001a: brtrue.s L_002b
        L_001c: ldloc.3 
        L_001d: ldftn instance void MyTest.Program/<>c__DisplayClass2::<Main>b__0()
        L_0023: newobj instance void [mscorlib]System.Action::.ctor(object, native int)
        L_0028: stloc.2 
        L_0029: br.s L_002b
        L_002b: ldloc.2 
        L_002c: stloc.1 
        L_002d: ldloc.0 
        L_002e: ldloc.1 
        L_002f: callvirt instance void [mscorlib]System.Collections.Generic.List`1<class [mscorlib]System.Action>::Add(!0)
        L_0034: nop 
        L_0035: nop 
        L_0036: ldloc.3 
        L_0037: dup 
        L_0038: ldfld int32 MyTest.Program/<>c__DisplayClass2::i
        L_003d: ldc.i4.1 
        L_003e: add 
        L_003f: stfld int32 MyTest.Program/<>c__DisplayClass2::i
        L_0044: ldloc.3 
        L_0045: ldfld int32 MyTest.Program/<>c__DisplayClass2::i
        L_004a: ldc.i4.5 
        L_004b: clt 
        L_004d: stloc.s CS$4$0000
        L_004f: ldloc.s CS$4$0000
        L_0051: brtrue.s L_0018
        L_0053: nop 
        L_0054: ldloc.0 

    //以下省略

    L_0009行,发现编译器为我们创建了一个类“<>c__DisplayClass2”,并且在循环内部每次会为这个类的一个实例变量 i 赋值。

    这个类的IL代码为:

    .class auto ansi sealed nested private beforefieldinit <>c__DisplayClass2
        extends [mscorlib]System.Object
    {
        .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
        .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
        {
        }
    
        .method public hidebysig instance void <Main>b__0() cil managed
        {
        }
    
    
        .field public int32 i
    
    }

    经过分析,会发现前面的这段代码实际和下面这段代码是一致的:

            static void Main(string[] args)
            {
                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 (Action t in lists)
                {
                    t();
                }
            }
    
            class TempClass
            {
                public int i;
                public void TempFuc()
                {
                    Console.WriteLine(i.ToString());
                }
            }

    这段代码演示的就是闭包对象。所谓闭包对象,指的是上面这种情形中的TempClass对象(在第一段代码中,就是编译器为我们生成的<>c__DisplayClass2对象)。如果匿名方法(lambda表达式)引用了某个局部变量,编译器就会自动将该引用提升到闭包对象中,即将for循环中的变量 i 修改成了引用闭包对象的公共变量 i 。这样,即使代码执行离开了原局部变量 i 的作用域(如for循环),包含该闭包对象的作用域还存在。理解了这一点,就理解了代码的输出了。

    要实现本建议开始时所预期的输出,可以将闭包对象的产生放在for循环内部:

            static void Main(string[] args)
            {
                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 (Action t in lists)
                {
                    t();
                }
            }

    此代码和下面的代码一致:

            static void Main(string[] args)
            {
                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 (Action t in lists)
                {
                    t();
                }
            }
    
            class TempClass
            {
                public int i;
                public void TempFuc()
                {
                    Console.WriteLine(i.ToString());
                }
            }

    转自:《编写高质量代码改善C#程序的157个建议》陆敏技

  • 相关阅读:
    Linux 常用命令--来自B站Up主codesheep
    如何区别调用python2和python3
    fastp 使用
    使用bash shell删除目录中的特定文件的3种方法
    python 正则表达式 finditer
    vcf format
    vcf文件(call variants得来的)怎么看变异是纯合还是杂合
    js Object.preventExtensions()
    js 对象的属性特征
    shell基础 以及 sed、awk
  • 原文地址:https://www.cnblogs.com/jesselzj/p/4732888.html
Copyright © 2011-2022 走看看