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中的闭包不是一个一说就能明白的概念,但是随着你往学习的深入,无论如何你都需要去了解这么一个东西 ...
随机推荐
- pytest跳过指定的测试或模块
参考Allure官方文档,pytest官方文档 实现setup/teardown 1.运行带指定标记的测试 @pytest.mark.tags ,这里的tags可以自定义 命令行执行:pytest - ...
- JavaScript变量语法扩展
1.更新变量 一个变量被重新赋值后,它原有的值会被覆盖,变量值将会以最后一次赋值为准. 2.同时声明多个变量 var age = 18 , address ='火影村' , gz = 2000 ; ...
- python之robotframework+pycharm测试框架
一.robotframework简介 Robot Framework是一款python编写的功能自动化测试框架.具备良好的可扩展性,支持关键字驱动,可以同时测试多种类型的客户端或者接口,可以进行分布式 ...
- 【朝夕Net社区技术专刊】Core3.1 WebApi集群实战专题---WebApi环境搭建运行发布部署篇
欢迎大家阅读<朝夕Net社区技术专刊>第1期 原文是我在知乎发表的,现在在这里分享! 我们致力于.NetCore的推广和落地,为更好的帮助大家学习,方便分享干货,特创此刊!很高兴你能成为首 ...
- ssh-copy-id to filter out any that...ERROR: Read from socket failed: Connection reset by peer
ssh-copy-id bigboss1 /usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter ...
- html css javascript实现弹弹球
效果如图: 原创代码: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> & ...
- JavaScript (六) js的基本语法 - - - Math 及 Date对象、String对象、Array对象
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 一.Math 1.Math对象的案例 var result= Math.max(10,20,30,40) ...
- Linux (八)服务
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 1.服务的概念 操作系统中在后台持续运行的程序,本身并没有操作界面,需要通过端口号访问和操作.CentO ...
- Linux (四) 基础命令 下
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 一.查看文件内容 1.命令 cat 对应单词:concatenate 作用:查看文件内容 常用参数: ...
- Java实现 LeetCode 341 扁平化嵌套列表迭代器
341. 扁平化嵌套列表迭代器 给你一个嵌套的整型列表.请你设计一个迭代器,使其能够遍历这个整型列表中的所有整数. 列表中的每一项或者为一个整数,或者是另一个列表.其中列表的元素也可能是整数或是其他列 ...