加入收藏 | 设为首页 | 会员中心 | 我要投稿 南通站长网 (https://www.0513zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 教程 > 正文

C#匿名技巧中的变量

发布时间:2021-11-19 15:56:47 所属栏目:教程 来源:互联网
导读:闭包的基本概念是:一个函数除了能够通过提供给它的参数与环境交互之外,还能同环境进行更大程度的互动。对于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代码看到了被捕获变量的工作原理。

(编辑:南通站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读