C#中闭包的陷阱
闭包定义
闭包(closure)在很多语言中都存在,在C#中,闭包是由匿名函数来表示的。C#中的闭包也叫做捕获的变量。当一个匿名函数引用了他所在作用域(一般情况下是一个方法)的局部变量时,为了能够顺利的执行匿名函数而不至于包含它的函数执行完之后线程栈弹出导致局部变量消失,会将这个变量的生命周期延长。这时就形成了闭包。闭包利用了匿名函数的一个特性:因为编译器会为匿名函数生成一个类(或结构),所以,提升匿名函数捕获的这个变量的生命周期的方法就是在把这个变量放到这个类中。此外,这个类中定义的方法既是这个匿名函数。
示例
for循环中的闭包陷阱
我们在使用lambda的时候会遇到闭包,在闭包中有一个陷阱是在for循环中产生的,先上代码:
class Program
{
static void Main(string[] args)
{
Action[] actions=new Action[];
for (int i = ; i < actions.Length; i++)
{
actions[i] = () => Console.WriteLine(i);
}
foreach (var item in actions)
{
item();
}
Console.ReadKey();
}
}
此时会看到Console输出的是一连串的5,这是因为C#中在for块中定义的int i会被当作外部变量来处理,我们在循环内部使用lambda的时候编译器会给我们生成一个类,比如这个代码如果是在Program中的main方法执行的时候这个类会在Program中生成,成为Program的一个内部类。这个类的主要作用是承载lambda表达式所代表的方法。当lambda表达式引用了一个局部变量时,为了保证这个变量的生命周期,这个局部变量会被编译器生成的这个类所捕捉,也就是说,这个局部变量的生命周期得到了提升,成为了一个类级别的字段了。
模拟闭包
我想说的是如何避免这个for循环中闭包的陷阱呢?先来模拟一下编译器在lambda背后的行为:
class Program
{
static void Main(string[] args)
{
Action[] actions = new Action[];
var innerClass = new InnerClass();//关键在这里
int i;//for循环中定义的局部变量是被当作外部变量来使用的,这是在C#中的实现。
for (i = ; i < actions.Length; i++)
{
innerClass.i = i;
actions[i] = innerClass.DoIt;
}
foreach (var item in actions)
{ item();
}
Console.ReadKey();
} public class InnerClass//这里是模拟编译器为lambda表达式生成的类,我暂时命名为InnerClass,实际上编译器生成的这个内部类有自己的命名规则。
{
public int i;//这个是捕获的for循环中的那个变量。 public void DoIt()
{
Console.WriteLine(i);
}
} }
闭包产生的这个陷阱关键就在于:
var innerClass = new InnerClass();//关键在这里
避免闭包陷阱
上面这句代码的位置,之所以会产生陷阱,就是因为innerClass捕获到的是最后的那个变量i的值,说到这里就不难想象如何去避免这个陷阱了,我们可以在for循环内部定义一个变量来保存每次的循环变量i的值:
class Program
{
static void Main(string[] args)
{
Action[] actions = new Action[]; for (int i = ; i < actions.Length; i++)
{
int j = i;//关键这里
actions[i] = Console.WriteLine(j);
}
foreach (var item in actions)
{ item();
}
Console.ReadKey();
}
}
我们在for循环的内部使用了一个变量先来捕获一遍i,然后编译器会将这个生成的类放在循环内部(而不是在for循环外部生成),每循环一次就生成一个新的来捕获。牛逼吧编译器?
C#中闭包的陷阱的更多相关文章
- JavaScript中的this陷阱的最全收集 没有之一
当有人问起你JavaScript有什么特点的时候,你可能立马就想到了单线程.事件驱动.面向对象等一堆词语,但是如果真的让你解释一下这些概 念,可能真解释不清楚.有句话这么说:如果你不能向一个6岁小孩解 ...
- 转:JavaScript中的this陷阱的最全收集
在其他地方看到的,觉得解释的狠详细,特此分享 当有人问起你JavaScript有什么特点的时候,你可能立马就想到了单线程.事件驱动.面向对象等一堆词语,但是如果真的让你解释一下这些概念,可能真解释不清 ...
- JavaScript中的this陷阱
当有人问起你JavaScript有什么特点的时候,你可能立马就想到了单线程.事件驱动.面向对象等一堆词语,但是如果真的让你解释一下这些概念,可能真解释不清楚.有句话这么说:如果你不能向一个6岁小孩解释 ...
- 关于js中闭包的理解
1.以前很不理解js中闭包的概念及使用,下面来看一下 function foo() { var a = 123; var b = 456; return function () { return a; ...
- 彻底搞清js中闭包(Closure)的概念
js中闭包这个概念对于初学js的同学来说, 会比较陌生, 有些难以理解, 理解起来非常模糊. 今天就和大家一起来探讨一下这个玩意. 相信大家在看完后, 心中的迷惑会迎然而解. 闭包概念: 闭包就是有权 ...
- HashTable和HashSet中的类型陷阱
HashTable和HashSet中的类型陷阱 发现这个陷阱的起因是这样的:我现在有上百万字符串,我准备用TopK算法统计出出现次数做多的前100个字符串. 首先我用Hashtable统计出了每个字符 ...
- 在Javascript中闭包(Closure)
在Javascript中闭包(Closure) 什么是闭包 “官方”的解释是:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. ...
- 对JavaScript中闭包的理解
在前端开发中闭包是一个很重要的知识点,是面试中一定会被问到的内容.之前我对闭包的理解主要是"通过闭包可以在函数外部能访问到函数内部的变量",对闭包运用的也很少,甚至自己写过闭包自己 ...
- 关于JS中闭包的问题
一直以来,我都以为我已经懂了JavaScript中闭包的概念,直到有一次小伙伴突然问我这个概念的时候,我才发现我根本不知道该怎来么跟他来讲述这个概念. 那时候我就知道我是自我欺骗,打肿脸充胖子了. 所 ...
随机推荐
- python3编写网络爬虫14-动态渲染页面爬取
一.动态渲染页面爬取 上节课我们了解了Ajax分析和抓取方式,这其实也是JavaScript动态渲染页面的一种情形,通过直接分析Ajax,借助requests和urllib实现数据爬取 但是javaS ...
- centos7下安装docker(15.7容器跨主机网络---calico)
Calico是一个纯三层的虚拟网络方案,Calico为每个容器分配一个IP,每个host都是router,把不同host的容器连接起来.与vxlan不同的是:calico不对数据包进行封装,不需要NA ...
- [SHOI2015]自动刷题机
嘟嘟嘟 这题就比较水了,毕竟只评了个蓝. 想一下发现满足单调性,所以可以二分找最大值. 但是最小值怎么办?刚开始我很zz的以为只要把判断条件从大于等于改成小于等于就行了,后来发现根本不对. 想了想因为 ...
- 【ES6】=>含义
=>是es6语法中的arrow function (x) => x + 6 相当于 function(x){ return x + 6; }; var ids = this.sels.ma ...
- Python 使用 distutils 工具安装的扩展包的卸载
Python 编写完扩展包并 build 好后,可以采用 $ sudo ./setup.py install 安装.采用这种方式安装的扩展包,可以使用 pip list 查看到,但不能直接使用 pip ...
- 环境部署(一):Linux下安装JDK
自动化测试的主要目的是为了执行回归测试.当然,为了模拟真实的用户操作,一般都是在UAT或者生产环境进行回归测试. 为了尽量避免内网和外网解析对测试结果的影响,将自动化测试服务部署在外网的服务器是比较好 ...
- js超时处理
var now = new Date(); var exitTime = now.getTime() + numberMillis; //numberMillis毫秒超时参数 while (true) ...
- Git安装以及常用命令(图文详解)
**Git安装以及常用命令** 1.下载安装Git,傻瓜式安装相信大家都会. 官网下载地址:[https://git-scm.com/downloads] 2.Git基本操作 (1)git --ver ...
- Java多线程核心技术(四)Lock的使用
本文主要介绍使用Java5中Lock对象也能实现同步的效果,而且在使用上更加方便. 本文着重掌握如下2个知识点: ReentrantLock 类的使用. ReentrantReadWriteLock ...
- 如何写出没有BUG的代码
1947年9月9日,美国海军准将 Grace Hopper 在哈佛学院计算机实验室里使用 Mark II 和 Mark III 计算机进行研究工作.她的团队跟踪到 Mark II 上的一个错误,操作人 ...