C#匿名方法中的变量

前面一篇文章看到了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(); }

代码的输出为:

C#匿名方法中的变量

在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; }

代码输出为:

C#匿名方法中的变量

为什么结果是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字段

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/b1bceb1bdeffd6e66bb76167a9ab46a9.html