博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C# 匿名函数引用局部变量解析
阅读量:2436 次
发布时间:2019-05-10

本文共 8480 字,大约阅读时间需要 28 分钟。

using System;namespace Application{	class Test	{		Action action;		public Test()		{			int value = 2046;			action = () => Console.WriteLine(value);		}		static void Main(string[] args)		{			Test test = new Test();			test.action();		}	}}

在 Test 构造函数里,局部变量 value 在构造函数执行结束后出栈,那么 C# 是如何实现在函数执行以后访问其中的局部变量的?

 

 

你必须了解:引用类型、值类型、引用、对象、值类型的值(简称值)。

关于引用、对象和值在内存的分配有如下几点规则: •对象分配在堆中。 •作为字段的引用分配在堆中(内嵌在对象中)。

•作为局部变量(参数也是局部变量)的引用分配在栈中。 •作为字段的值分配在堆中(内嵌在对象中)。
•作为局部变量(参数也是局部变量)的值用分配在栈中。 •局部变量只能存活于所在的作用域(方法中的大括号确定了作用域的长短)。

注:按值传递和按引用传递也是需要掌握的知识点,C# 默认是按值传递的。

概念

内层的函数可以引用包含在它外层的函数的变量,即使外层函数的执行已经终止。但该变量提供的值并非变量创建时的值,而是在父函数范围内的最终值。

条件

闭包是将一些执行语句的封装,可以将封装的结果像对象一样传递,在传递时,这个封装依然能够访问到原上下文。

形成闭包有一些值得总结的非必要条件:
1、嵌套定义的函数。
2、匿名函数。
3、将函数作为参数或者返回值。
4、在.NET中,可以通过匿名委托形成闭包:函数可以作为参数传递,也可以作为返回值返回,或者作为函数变量。而在.NET中,这都可以通过委托来实现。这些是实现闭包的前提。

闭包的优点:

  使用闭包,我们可以轻松的访问外层函数定义的变量,这在匿名方法中普遍使用。比如有如下场景,在winform应用程序中,我们希望做这么一个效果,当用户关闭窗体时,给用户一个提示框。

private void Form1_Load(object sender, EventArgs e){       string msg= "您将关闭当前对话框";       this.FormClosing += delegate       {            MessageBox.Show(msg);       };}

匿名函数很容易的访问到了作用域之外的变量。

闭包陷阱

全局变量

public static int i;//这个不是闭包static void Main(string[] args){    //定义动作组    List
actions = new List
(); for (int counter = 0; counter < 10; counter++) { i = counter; actions.Add(() => Console.WriteLine(i)); } i = 123; //执行动作 foreach (Action action in actions) action(); Console.ReadKey();}
public static int i;//这个不是闭包static void TempMethod(){    Console.WriteLine(i);}static void Main(string[] args){    //定义动作组    List
actions = new List
(); for (int counter = 0; counter < 10; counter++) { i = counter; actions.Add(new Action(TempMethod)); } //执行动作 foreach (Action action in actions) action(); Console.ReadKey();}

闭包示例一

static void Main(){    int i;//[1]闭包一    //定义动作组    List
actions = new List
(); for (int counter = 0; counter < 10; counter++) { i = counter; actions.Add(() => Console.WriteLine(i)); } //执行动作 foreach (Action action in actions) action(); Console.ReadKey();}

运行结果:

这里写图片描述

显然这个结果不是我们想要的,上面的程序相当于下面的示例代码:

static void Main(){    TempClass tc = new TempClass();    //定义动作组    List
actions = new List
(); for (int counter = 0; counter < 10; counter++) { tc.i = counter; actions.Add(tc.TempMethod); } //执行动作 foreach (Action action in actions) action(); Console.ReadKey();}class TempClass{ public int i; public void TempMethod() { Console.WriteLine(i); }}

闭包示例二

static void Main(){    //定义动作组    List
actions = new List
(); for (int i = 0; i < 10; i++)//[3]闭包二 { actions.Add(() => Console.WriteLine(i)); } //执行动作 foreach (Action action in actions) action(); Console.ReadKey();}

上面的程序相当于下面的示例代码:

static void Main(){    //定义动作组    List
actions = new List
(); TempClass tc = new TempClass(); for (tc.i = 0; tc.i < 10; tc.i++) { actions.Add(new Action(tc.TempMethod)); } //执行动作 foreach (Action action in actions) action(); Console.ReadKey();}class TempClass{ public int i; public void TempMethod() { Console.WriteLine(i); }}

运行结果:

这里写图片描述
这个结果也不是我们预期的。

分析

以示例一为例说明代码运行机制:

首先:C#编译器 为我们生成了一个 ‘<>c__DisplayClass0_0’的类,一个 “< Main > b__0”的方法 和 一个 变量 i。这个public int32 i 的变量就是程序一开始我们定义的变量i,现在被包装到了类中。

这里写图片描述

.method assembly hidebysig instance void         '
b__0'() cil managed{ // 代码大小 13 (0xd) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldfld int32 'CSharp闭包之局部变量一'.Program/'<>c__DisplayClass0_0'::i IL_0006: call void [mscorlib]System.Console::WriteLine(int32) IL_000b: nop IL_000c: ret} // end of method '<>c__DisplayClass0_0'::'
b__0'

上面这个是”< Main > b__0”方法的IL代码:就是输出

System.Console::WriteLine(int32)

下面是Main主程序的IL代码:

.method private hidebysig static void  Main() cil managed{  .entrypoint  // 代码大小       140 (0x8c)  .maxstack  4  .locals init ([0] class 'CSharp闭包之局部变量一'.Program/'<>c__DisplayClass0_0' 'CS$<>8__locals0',           [1] class [mscorlib]System.Collections.Generic.List`1
actions, [2] int32 counter, [3] class [mscorlib]System.Action V_3, [4] bool V_4, [5] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator
V_5, [6] class [mscorlib]System.Action action) IL_0000: newobj instance void 'CSharp闭包之局部变量一'.Program/'<>c__DisplayClass0_0'::.ctor() IL_0005: stloc.0 IL_0006: nop IL_0007: newobj instance void class [mscorlib]System.Collections.Generic.List`1
::.ctor() IL_000c: stloc.1 IL_000d: ldc.i4.0 IL_000e: stloc.2 IL_000f: br.s IL_0044 IL_0011: nop IL_0012: ldloc.0 IL_0013: ldloc.2 IL_0014: stfld int32 'CSharp闭包之局部变量一'.Program/'<>c__DisplayClass0_0'::i IL_0019: ldloc.1 IL_001a: ldloc.0 IL_001b: ldfld class [mscorlib]System.Action 'CSharp闭包之局部变量一'.Program/'<>c__DisplayClass0_0'::'<>9__0' IL_0020: dup IL_0021: brtrue.s IL_0039 IL_0023: pop IL_0024: ldloc.0 IL_0025: ldloc.0 IL_0026: ldftn instance void 'CSharp闭包之局部变量一'.Program/'<>c__DisplayClass0_0'::'
b__0'() IL_002c: newobj instance void [mscorlib]System.Action::.ctor(object, native int) IL_0031: dup IL_0032: stloc.3 IL_0033: stfld class [mscorlib]System.Action 'CSharp闭包之局部变量一'.Program/'<>c__DisplayClass0_0'::'<>9__0' IL_0038: ldloc.3 IL_0039: callvirt instance void class [mscorlib]System.Collections.Generic.List`1
::Add(!0) IL_003e: nop IL_003f: nop IL_0040: ldloc.2 IL_0041: ldc.i4.1 IL_0042: add IL_0043: stloc.2 IL_0044: ldloc.2 IL_0045: ldc.i4.s 10 IL_0047: clt IL_0049: stloc.s V_4 IL_004b: ldloc.s V_4 IL_004d: brtrue.s IL_0011 IL_004f: nop IL_0050: ldloc.1 IL_0051: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator
class [mscorlib]System.Collections.Generic.List`1
::GetEnumerator() IL_0056: stloc.s V_5 .try { IL_0058: br.s IL_006b IL_005a: ldloca.s V_5 IL_005c: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator
::get_Current() IL_0061: stloc.s action IL_0063: ldloc.s action IL_0065: callvirt instance void [mscorlib]System.Action::Invoke() IL_006a: nop IL_006b: ldloca.s V_5 IL_006d: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator
::MoveNext() IL_0072: brtrue.s IL_005a IL_0074: leave.s IL_0085 } // end .try finally { IL_0076: ldloca.s V_5 IL_0078: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator
IL_007e: callvirt instance void [mscorlib]System.IDisposable::Dispose() IL_0083: nop IL_0084: endfinally } // end handler IL_0085: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey() IL_008a: pop IL_008b: ret} // end of method Program::Main

  编译器生成IL代码后,将作用域外的变量i,放到了匿名类型‘<>c__DisplayClass0_0’中当做成员字段来使用,由此,本来应该在堆栈上的int型i,被编译器包装成了object类类型的成员字段,而object被存储在堆中。

  其实C#并不会对每个需要捕获的值类型变量进行装箱操作,而是把所有捕获的变量统统放到同一个大“箱子”里——当编译器遇到需要变量捕获的情况时,它会默默地在后台构造一个匿名类型,这个匿名类型包含了每一个闭包所捕获的变量(包括值类型变量和引用类型变量)作为它的一个公有字段。这样,编译器就可以维护那些在匿名函数或lambda表达式中出现的外部变量了。

总结

编译器将闭包引用的局部变量转换为匿名类型的字段,导致了局部变量分配在堆中。

避免闭包陷阱

如何避免闭包陷阱呢?C#中普遍的做法是,将匿名函数引用的变量用一个临时变量保存下来,然后在匿名函数中使用临时变量。

闭包示例三

static void Main(){    //定义动作组    List
actions = new List
(); for (int counter = 0; counter < 10; counter++) { int i;//[1]闭包三 i = counter; //int copy = counter;//换种写法 actions.Add(() => Console.WriteLine(i)); } //执行动作 foreach (Action action in actions) action(); Console.ReadKey();}

上面的程序相当于下面的示例代码:

static void Main(){    //定义动作组    List
actions = new List
(); for (int counter = 0; counter < 10; counter++) { TempClass tc = new TempClass(); tc.i = counter; actions.Add(tc.TempMethod); } //执行动作 foreach (Action action in actions) action(); Console.ReadKey();}class TempClass{ public int i; public void TempMethod() { Console.WriteLine(i); }}

运行结果:

这里写图片描述

与此同时,我们也可以在知道闭包的副作用的情况下(内层的函数可以引用包含在它外层的函数的变量,即使外层函数的执行已经终止。但该变量提供的值并非变量创建时的值,而是在父函数范围内的最终值)加以利用。

 

转自:https://blog.csdn.net/cjolj/article/details/60868305

你可能感兴趣的文章
深度探索C++对象模型 ( 第四部分 )(转)
查看>>
MySQL中的SQL特征(转)
查看>>
使用JBuilder和WTK2.2搭建MIDP1.0和MIDP2.0开发环境(转)
查看>>
Symbian命名规则(翻译)(转)
查看>>
windows server 2003的设置使用(转)
查看>>
优化Win2000的NTFS系统(转)
查看>>
IE漏洞可使黑客轻易获取私人信息(转)
查看>>
脱机备份与恢复实战(转)
查看>>
WLINUX下的DNS服务器设置(转)
查看>>
游戏引擎剖析(二)(转)
查看>>
sms发mms C语言源码(转)
查看>>
窝CDMA网络中移动IP接入Internet(转)
查看>>
实现MMS增值业务的关键技术(转)
查看>>
Vista被破解 一个小程序可成功激活(转)
查看>>
SEO作弊常见方法和形式(转)
查看>>
蓝芽技术的原理和应用(2)(转)
查看>>
解决接通电源后自动开机问题(转)
查看>>
实例编程:用VC写个文件捆绑工具(转)
查看>>
教你如何用手工迅速剿灭QQ广告弹出木马(转)
查看>>
Windows系统维护完全图形化攻略(转)
查看>>