zoukankan      html  css  js  c++  java
  • 匿名方法中的变量

    前面一篇文章看到了C# 2.0中通过匿名方法来简化委托,下面来看看匿名方法中的变量。

    闭包和不同的变量类型

    闭包的基本概念是:一个函数除了能够通过提供给它的参数与环境交互之外,还能同环境进行更大程度的互动。对于C# 2.0中出现的匿名方法的闭包表现为,匿名方法能使用在声明该匿名方法的方法内部定义的局部变量

    在进一步了解闭包之前,我们先看看下面两个术语:

    外部变量(outer variable):是指其作用域(scope)包括一个匿名方法的局部变量或参数(ref和out参数除外)

    被捕捉的外部变量(captured outer variable):它是在匿名方法内部使用的外部变量

    结合上面的解释,来看一个被捕获的变量的例子:

    private static void EnclosingMethod()
    {
        //未被捕获的外部变量
        int outerVariable = 2;
        //被匿名方法捕获的外部变量
        string capturedVariable = "captured variable";
    
        if (DateTime.Now.Hour == 23)
        {
            //普通局部变量
            int normalLocalVarialbe = 3;
            Console.WriteLine(normalLocalVarialbe);
        }
    
        Action x = delegate
        {
            //匿名方法的局部变量
            string anonymousLocal = "local variable of anonymous method";
            //获得被捕获的外部变量
            Console.WriteLine(capturedVariable);
            Console.WriteLine(anonymousLocal);
        };
        x();
    }

    一个变量被捕获之后,被匿名方法捕获的是这个变量,为不是创建委托实例时该变量的值。下面通过一个例子来看看这句描述。

    private static void CapturedVariableTesting()
    {
        string captured = "before x is created";
    
        Action x = delegate
        {
            Console.WriteLine(captured);
            captured = "changed by x";
        };
    
        captured = "changed before x is invoked";
        x();
    
        Console.WriteLine(captured);
    
        captured = "before second invocation";
        x();
    }

    代码的输出为:

    在CapturedVariableTesting这个方法中,我们始终都是在使用同一个被捕获变量captured;也就是说,在匿名方法外对被捕获变量的修改,在匿名方法内部是可见的,反之亦然。

    捕捉变量的用途

    闭包的出现给我们带来很多的便利,直接利用被捕获变量可以简化编程,避免专门创建一些类来存储一个委托需要处理的信息。

    看一个例子,我们给定一个上限,来获取List中所有小于这个上限的数字。

    private static List<int> FindAllLessThan(List<int> numList, int upperLimitation)
    {
        return numList.FindAll(delegate(int num)
        {
            return num < upperLimitation;
        });
    }

    由于闭包的出现,我们不用将upperLimitation这个变量以函数参数的形式传给匿名函数,在匿名方法中可以直接使用这个被捕获的变量。

    捕获变量的工作原理

    前面看到的例子都比较简单,下面我们看一个稍微复杂的例子:

    static void Main(string[] args)
    {
        Action x = CreateDelegateInstance();
        x();
        x();
        Console.Read();
    }
    
    private static Action CreateDelegateInstance()
    {
        int counter = 5;
        Action ret = delegate 
        {
            Console.WriteLine(counter);
            counter++;
        };
    
        ret();
        return ret;
    }

    代码输出为:

    为什么结果是5,6,7?变量counter在CreateDelegateInstance方法结束后为什么没有被销毁?

    当我们查看这个例子的IL代码时,发现编译器为我们创建了一个类"<>c__DisplayClass1"。

    .class nested private auto ansi sealed beforefieldinit '<>c__DisplayClass1'
        extends [mscorlib]System.Object
    {
        .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
            01 00 00 00
        )
        // Fields
        .field public int32 counter
    
        // Methods
        .method public hidebysig specialname rtspecialname 
            instance void .ctor () cil managed 
        {
            // Method begins at RVA 0x2078
            // Code size 7 (0x7)
            .maxstack 8
    
            IL_0000: ldarg.0
            IL_0001: call instance void [mscorlib]System.Object::.ctor()
            IL_0006: ret
        } // end of method '<>c__DisplayClass1'::.ctor
    
        .method public hidebysig 
            instance void '<CreateDelegateInstance>b__0' () cil managed 
        {
            // Method begins at RVA 0x2080
            // Code size 28 (0x1c)
            .maxstack 8
    
            IL_0000: nop
            IL_0001: ldarg.0
            IL_0002: ldfld int32 AnonymousMethod.Program/'<>c__DisplayClass1'::counter
            IL_0007: call void [mscorlib]System.Console::WriteLine(int32)
            IL_000c: nop
            IL_000d: ldarg.0
            IL_000e: dup
            IL_000f: ldfld int32 AnonymousMethod.Program/'<>c__DisplayClass1'::counter
            IL_0014: ldc.i4.1
            IL_0015: add
            IL_0016: stfld int32 AnonymousMethod.Program/'<>c__DisplayClass1'::counter
            IL_001b: ret
        } // end of method '<>c__DisplayClass1'::'<CreateDelegateInstance>b__0'
    
    } // end of class <>c__DisplayClass1

    而在CreateDelegateInstance方法的IL代码中可以看到,CreateDelegateInstance的局部变量counter实际上就是"<>c__DisplayClass1"对象的counter字段

    IL_0000: newobj instance void AnonymousMethod.Program/'<>c__DisplayClass1'::.ctor()
    IL_0005: stloc.1
    IL_0006: nop
    IL_0007: ldloc.1
    IL_0008: ldc.i4.5
    IL_0009: stfld int32 AnonymousMethod.Program/'<>c__DisplayClass1'::counter

    通过上面的分析可以看到,编译器创建了一个额外的类来容纳变量,CreateDelegateInstance方法拥有该类的一个实例引用,并通过这个引用访问counter变量。counter这个局部变量并不是在"调用栈"空间上,这也就解释了为什么函数返回后,这个变量没有被销毁。

    在上面的例子中只有一个委托实例,下面再看一个拥有多个委托实例的例子:

    static void Main(string[] args)
    {
        List<Action> list = new List<Action>();
    
        for(int index = 0; index < 5; index++)
        {
            int counter = index * 10;
            list.Add(delegate
            {
                Console.WriteLine(counter);
                counter++;
            });
        }
    
        foreach (Action x in list)
        {
            x();
        }
    
        list[0]();
        list[0]();
    
        list[1]();
    
        Console.Read();
    }

    代码输出为:

    通过输出可以看到,每个委托实例将捕获不同的变量。

    所以被捕获变量的声明期可以总结为:对于一个被捕获的变量,只要还有任何委托实例在引用它,它就会一直存在;当一个变量被捕获时,捕获的是变量的"实例"。

    总结

    本文介绍了闭包和不同的变量类型。在匿名方法中,通过被捕获变量,我们可以使用"现有"的上下文信息,而不必专门设置额外的类型来存储一些已知的数据

    同时,介绍了被捕获变量的生命期,通过IL代码看到了被捕获变量的工作原理。

  • 相关阅读:
    ACM题集以及各种总结大全
    ACM题集以及各种总结大全
    线段树题集
    线段树题集
    POJ 1159 Palindrome【LCS+滚动数组】【水题】
    POJ 1159 Palindrome【LCS+滚动数组】【水题】
    开课博客
    第一周学习进度
    开学测试
    寒假总结
  • 原文地址:https://www.cnblogs.com/wilber2013/p/4298104.html
Copyright © 2011-2022 走看看