C#中的闭包和意想不到的坑
虽然闭包主要是函数式编程的玩意儿,而C#的最主要特征是面向对象,但是利用委托或lambda表达式,C#也可以写出具有函数式编程风味的代码。同样的,使用委托或者lambda表达式,也可以在C#中使用闭包。
根据WIKI的定义,闭包又称语法闭包或函数闭包,是在函数式编程语言中实现语法绑定的一种技术。闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)。闭包也可以延迟变量的生存周期。
嗯。。看定义好像有点迷糊,让我们看看下面的例子吧
class Program
{
static Action CreateGreeting(string message)
{
return () => { Console.WriteLine("Hello " + message); };
}
static void Main()
{
Action action = CreateGreeting("DeathArthas");
action();
}
}
这个例子非常简单,用lambda表达式创建一个Action对象,之后再调用这个Action对象。
但是仔细观察会发现,当Action对象被调用的时候,CreateGreeting方法已经返回了,作为它的实参的message应该已经被销毁了,那么为什么我们在调用Action对象的时候,还是能够得到正确的结果呢?
原来奥秘就在于,这里形成了闭包。虽然CreateGreeting已经返回了,但是它的局部变量被返回的lambda表达式所捕获,延迟了其生命周期。怎么样,这样再回头看闭包定义,是不是更清楚了一些?
闭包就是这么简单,其实我们经常都在使用,只是有时候我们都不自知而已。比如大家肯定都写过类似下面的代码。
void AddControlClickLogger(Control control, string message)
{
control.Click += delegate
{
Console.WriteLine("Control clicked: {0}", message);
}
}
这里的代码其实就用了闭包,因为我们可以肯定,在control被点击的时候,这个message早就超过了它的声明周期。合理使用闭包,可以确保我们写出在空间和时间上面解耦的委托。
不过在使用闭包的时候,要注意一个陷阱。因为闭包会延迟局部变量的生命周期,在某些情况下程序产生的结果会和预想的不一样。让我们看看下面的例子。
class Program
{
static List<Action> CreateActions()
{
var result = new List<Action>();
for(int i = 0; i < 5; i++)
{
result.Add(() => Console.WriteLine(i));
}
return result;
}
static void Main()
{
var actions = CreateActions();
for(int i = 0;i<actions.Count;i++)
{
actions[i]();
}
}
}
这个例子也非常简单,创建一个Action链表并依次执行它们。看看结果


相信很多人看到这个结果的表情是这样的!!难道不应该是0,1,2,3,4吗?出了什么问题?
刨根问底,这儿的问题还是出现在闭包的本质上面,作为“闭包延迟了变量的生命周期”这个硬币的另外一面,是一个变量可能在不经意间被多个闭包所引用。
在这个例子里面,局部变量i同时被5个闭包引用,这5个闭包共享i,所以最后他们打印出来的值是一样的,都是i最后退出循环时候的值5。
要想解决这个问题也很简单,多声明一个局部变量,让各个闭包引用自己的局部变量就可以了。
//其他都保持与之前一致
static List<Action> CreateActions()
{
var result = new List<Action>();
for (int i = 0; i < 5; i++)
{
int temp = i; //添加局部变量
result.Add(() => Console.WriteLine(temp));
}
return result;
}

这样各个闭包引用不同的局部变量,刚刚的问题就解决了。
除此之外,还有一个修复的方法,在创建闭包的时候,使用foreach而不是for。至少在C# 7.0 的版本上面,这个问题已经被注意到了,使用foreach的时候编译器会自动生成代码绕过这个闭包陷阱。
//这样fix也是可以的
static List<Action> CreateActions()
{
var result = new List<Action>();
foreach (var i in Enumerable.Range(0,5))
{
result.Add(() => Console.WriteLine(i));
}
return result;
}
这就是在闭包在C#中的使用和其使用中的一个小陷阱,希望大家能通过老胡的文章了解到这个知识点并且在开发中少走弯路!
C#中的闭包和意想不到的坑的更多相关文章
- 让你分分钟学会Javascript中的闭包
Javascript中的闭包 前面的话: 闭包,是 javascript 中重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是ECMA规范给的定义,如果没有实战经验,你很难从定义去理解它 ...
- javascript中的闭包解析
学习javaScript已经有一段时间了,在这段时间里,已经感受到了JavaScript的种种魅力,这是一门神奇的语言,同时也是一门正在逐步完善的语言,相信在大家的逐步修改中,这门语言会逐步的完善下去 ...
- Javascript中的闭包(转载)
前面的话: 闭包,是 javascript 中重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是ECMA规范给的定义,如果没有实战经验,你很难从定义去理解它.下面是作者从作用域链慢慢讲到 ...
- 狗日的Javascript中的闭包
前面的话: 闭包,是 javascript 中重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是ECMA规范给的定义,如果没有实战经验,你很难从定义去理解它.下面是作者从作用域链慢慢讲到 ...
- 【原】理解javascript中的闭包
闭包在javascript来说是比较重要的概念,平时工作中也是用的比较多的一项技术.下来对其进行一个小小的总结 什么是闭包? 官方说法: 闭包是指有权访问另一个函数作用域中的变量的函数.创建闭包的常见 ...
- 难道这就是JavaScript中的"闭包"
其实对于JavaScript中的"闭包"还没真正理解,这次在实际Coding中似乎遇到了"闭包"的问题,仅此摘录,以待深究. 表现为jQuery的post方法回 ...
- 【Fine原创】JMeter分布式测试中踩过的那些坑
最近因为项目需要,研究了性能测试的相关内容,并且最终选用了jmeter这一轻量级开源工具.因为一直使用jmeter的GUI模式进行脚本设计,到测试执行阶段工具本身对资源的过量消耗给性能测试带来了瓶颈, ...
- 【原】如何在jQuery中实现闭包
原生JS中,闭包虽好用,但是很难用好,在jQuery中一样,都有一些点需要我们注意.jQuery中使用闭包的常见情况有以下几种: 1.$(document).ready()的参数 我们在写jQuery ...
- 说说Python中的闭包 - Closure
转载自https://segmentfault.com/a/1190000007321972 Python中的闭包不是一个一说就能明白的概念,但是随着你往学习的深入,无论如何你都需要去了解这么一个东西 ...
随机推荐
- ubuntu 下安装 mysql 启动报错:ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)
环境: ubuntu LTS 18.04.1 mysql Ver 14.14 Distrib 5.7.29, for Linux (x86_64) 初探 linux,按照如下安装完mysql sudo ...
- 发光加载环动画-纯CSS动画效果-如何创建CSS3旋转预加载器(参照https://www.bilibili.com/video/BV1V4411C7z5?from=search&seid=9741275927942612817)
//css部分 body{ margin:; padding:; background: #262626; } .ring{ position: absolute; top:50%; left: 50 ...
- linux:基本指令
指令标准格式 指令主体 [选项][操作对象] 基础指令 ls 1.ls 列出当前工作目录下的所有文件/文件夹的名称 2.ls 路径 列出指定路径下的所有文件/文件夹的名称 路径: 1.相对路径 会用 ...
- Java中方法的重载与重写
1.方法的名字和参数列表称为方法的签名:每个方法具有唯一与其对应的签名: 2.方法的重载:在某个类中,存在具有多个相同名字不同参数列表的方法,称之为重载: 被重载的方法必须改变参数列表(参数个数或类型 ...
- zabbix通过IPMI监控服务器传感器参数
一.需求:机房dell服务器和IBM服务器皆有主板管理接口iDRAC和iMM,上周已为服务器管理接口配置了ip地址,考虑通过zabbix实现对服务器传感器参数的实时监控.使用DELL-DL1300服务 ...
- 数据库之 MySQL --- 数据处理 之 表的约束与分页(七)
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 1.约束 :为了保证数据的一致性和完整性,SQL规范以约束的方式对表数据进行额外的条件限制 ...
- Linux (八)服务
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 1.服务的概念 操作系统中在后台持续运行的程序,本身并没有操作界面,需要通过端口号访问和操作.CentO ...
- footer部分,当页面主题内容不满一屏时,始终位于页面底部
比如上面这种情况,footer部分本来应该位于最底部,但是main内容太少导致连在一起,影响美观 解决方案: 给footer加上margin-top:负值 值的大小为footer的高度 写了个小dem ...
- (Java实现) 洛谷 P1106 删数问题
题目描述 键盘输入一个高精度的正整数NN(不超过250250位) ,去掉其中任意kk个数字后剩下的数字按原左右次序将组成一个新的正整数.编程对给定的NN和kk,寻找一种方案使得剩下的数字组成的新数最小 ...
- (Java实现)洛谷 P2095 营养膳食
题目描述 Mr.L正在完成自己的增肥计划. 为了增肥,Mr.L希望吃到更多的脂肪.然而也不能只吃高脂肪食品,那样的话就会导致缺少其他营养.Mr.L通过研究发现:真正的营养膳食规定某类食品不宜一次性吃超 ...