C#中的闭包是怎么捕获变量的

开发 后端
闭包是可以包含自由(未绑定)变量的代码块;这些变量不是在这个代码块或者任何全局上下文中定义的,而是在定义代码块的环境中定义。本文主要介绍了C#中的闭包是怎么捕获变量的。

简单来讲,闭包允许你将一些行为封装,将它像一个对象一样传来递去,而且它依然能够访问到原来***次声明时的上下文。这样可以使控制结构、逻辑操作等从调用细节中分离出来。访问原来上下文的能力是闭包区别一般对象的重要特征,尽管在实现上只是多了一些编译器技巧。

我们知道,在匿名方法或者lambda中,可以访问或者修改该匿的定义范围内的变量。例如:

  1. int num = 1;   
  2. Func<int> incNum = () => ++num; 

其中lambda表达式使用了在其外部定义的变量num。我们可以认为该段lambda语句块构成了一个闭包,而这个闭包捕获了外部变量num。

好了,不说那么多让人看着难受的定义套话了。我们进入正题,看看在C#中变量是如何被捕获的。来看一个例子:

  1. public Func<String> CreateFunction()   
  2. {   
  3. String str = "我的幸运数字是";   
  4. int num = 17;   
  5. Func<String> func = () => str + num;   
  6. return func;   

在这个例子中,定义了一个返回一个函数的方法CreateFunction。返回的函数构成了一个闭包,该闭包捕获了两个变量:String类型的str和int类型的num。

好了,我们现在可以这样使用这个函数了:

  1. Func<String>   
  2. myFunc = CreateFunction();   
  3. String result = myFunc();  

我们来分析一下这两行代码实际都干了什么。***行很容易理解,我们把方法CreateFunction生成的匿名函数赋值给了委托myFunc。

第二行更好理解,我们执行了myFunc,并将返回结果赋值给了变量result。我们再深入思考一下:在执行myFunc的时候,会访问到在CreateFunction中定义两个变量str与num。

虽然这时CreateFunction的栈帧早就被销毁了,其内部定义的变量至今也“生死不明”了,但是因为我们知道这两个变量已经被闭包所捕获了,所以我们坚信这两个变量截至目前为止还是可以访问的!

对于str对象,鉴于它是一个引用类型,所以只要有存在某个“东西”一直保存着对它的引用,它就不会被销毁。这样我们完全不用担心在我们需要它时,编译器或运行时会告诉我们它被弄丢了。

然而对于num,情况就有些不同了。num是一个值类型。我们知道值类型是存活在栈上的,我们也知道它所存在的那个栈帧(也就是CreateFunction的帧)在CreateFunction执行完毕后就会被销毁,然后其上存在的任何值类型也会被一并的销毁,这其中当然包括我们所关注的变量num了。

那么,我们为什么还能安全的访问num呢?C#中的变量捕获机制究竟有什么神奇之处,可以让值类型拥有违反常规的生存周期呢?装箱!你可能会立刻想到,把每个值类型都装到一个对象里,我们就可以让这个值类型拥有和那个包裹它的对象相同的寿命了。

不过,这并不是C#实现者所选择的方式!C#并不会对每个需要捕获的值类型变量进行装箱操作,而是把所有捕获的变量统统放到同一个大“箱子”里——当编译器遇到需要变量捕获的情况时,它会默默地在后台构造一个类型,这个类型包含了每一个闭包所捕获的变量(包括值类型变量和引用类型变量)作为它的一个公有字段。这样,编译器就可以

维护那些在匿名函数或lambda表达式中出现的外部变量了。

更进一步,如果我们使用ILDASM工具查看CreateFunction方法的IL代码,我们会发现编译器压根就没有声明num和str变量。取而代之的是声明了一个类型名和实例名都及其难看的包装对象。这个玩意儿就是我们上面所说的那个被编译器默默生成,保存了所有捕获变量的引用的对象。

我们还可以看到,在CreateFunction方法,C#源代码内所有对str和num的操作,在IL中都被转换成了对包装对象的同名公有成员的操作。顺便说一句,就连我们构造的那个lambda表达式“() => str + num”现在都被编译器转换成了这个包装对象的一个方法!

【编辑推荐】

  1. 使用ASP.NET操作IIS7中使用应用程序
  2. SqlParameter的作用与用法
  3. ASP.net的身份验证方式FORMS
  4. C#使用MemoryStream类读写内存
  5. C#控件的闪烁问题解决方法总结
责任编辑:于铁 来源: 博客园
相关推荐

2011-08-05 09:33:30

Func局部变量作用域

2023-09-11 08:20:17

对象闭包底层

2021-10-26 13:18:52

Go底层函数

2022-06-08 08:01:20

useEffect数组函数

2024-01-08 08:35:28

闭包陷阱ReactHooks

2024-11-26 00:45:29

free区域字段

2024-03-11 16:13:26

C#编程开发

2019-11-07 21:51:18

闭包前端函数

2012-11-29 10:09:23

Javascript闭包

2016-11-01 09:18:33

Python闭包

2020-05-28 15:41:48

微软C+语言

2022-05-30 16:19:26

C#多态底层虚方法

2023-11-02 08:53:26

闭包Python

2024-01-22 09:51:32

Swift闭包表达式尾随闭包

2011-05-12 18:26:08

Javascript作用域

2009-07-31 18:39:31

C#中foreach引

2023-01-09 08:00:41

JavaScript闭包

2020-12-16 11:09:27

JavaScript语言开发

2009-07-22 07:43:00

Scala闭包

2009-08-03 17:07:31

C#固定的和活动的变量
点赞
收藏

51CTO技术栈公众号