理解C#中的闭包
闭包的概念
内层的函数可以引用包含在它外层的函数的变量,即使外层函数的执行已经终止。但该变量提供的值并非变量创建时的值,而是在父函数范围内的最终值。
闭包的优点
使用闭包,我们可以轻松的访问外层函数定义的变量,这在匿名方法中普遍使用。比如有如下场景,在winform应用程序中,我们希望做这么一个效果,当用户关闭窗体时,给用户一个提示框。我们将添加如下代码:
private void Form1_Load(object sender, EventArgs e)
{
string tipWords = "您将关闭当前对话框";
this.FormClosing += delegate
{
MessageBox.Show(tipWords);
};
}
若不使用匿名函数,我们就需要使用其他方式来将tipWords变量的值传递给FormClosing注册的处理函数,这就增加了不必要的工作量。
闭包陷阱
应用闭包,我们要注意一个陷阱。比如有一个用户信息的数组,我们需要遍历每一个用户,对各个用户做处理后输出用户名。
public class UserModel
{
public string UserName
{
get;
set;
} public int UserAge
{
get;
set;
}
}
List<UserModel> userList = new List<UserModel>
{
new UserModel{ UserName="jiejiep", UserAge = },
new UserModel{ UserName="xiaoyi", UserAge = },
new UserModel{ UserName="zhangzetian", UserAge=}
}; for(int i = ; i < 3; i++)
{
ThreadPool.QueueUserWorkItem((obj) =>
{
//TODO
//Do some process...
//...
Thread.Sleep();
UserModel u = userList[i];
Console.WriteLine(u.UserName);
});
}
我们预期的输出是, jiejiep, xiaoyi, zhangzetian
但是实际我们运行后发现,程序会报错,提示索引超出界限。
为什么没有达到我们预期的效果呢?让我们再来看一下闭包的概念。内层函数引用的外层函数的变量的最终值。就是说,当线程中执行方法时,方法中的i参数的值,始终是userList.Count。原来如此,那我们该如何
避免闭包陷阱呢?C#中普遍的做法是,将匿名函数引用的变量用一个临时变量保存下来,然后在匿名函数中使用临时变量。
List<UserModel> userList = new List<UserModel>
{
new UserModel{ UserName="jiejiep", UserAge = },
new UserModel{ UserName="xiaoyi", UserAge = },
new UserModel{ UserName="zhangzetian", UserAge=}
}; for(int i = ; i < 3; i++)
{
UserModel u = userList[i];
ThreadPool.QueueUserWorkItem((obj) =>
{
//TODO
//Do some process...
//...
Thread.Sleep();
//UserModel u = userList[i];
Console.WriteLine(u.UserName);
});
}
我们再运行来看,输出依次为 jiejiep,xiaoyi, zhangzetian.注意,每次的输出顺序可能不同。
NET编译器与闭包
提出了问题,给出了解决方案,我们总算知道该怎么正确使用闭包了。但是dotNET是如何实现闭包的呢?执着的程序猿们,不会满足于这种表象的解决方案,让我们来看看dotNET是如何实现闭包的。我们可以微软提供的isdasm.exe来查看编译后的代码。我们先来看看有问题的代码。将IL代码翻译后,可以得到如下的伪代码。
public class TempClass5
{
public List<UserModel> UserList; } public class TempClass8
{ public int i = ; public TempClass5 c5; public ShowMessage(object o)
{ Thread.Sleep(); Console.WriteLine(c5.UserList[i].UserName);
}
}
public class Program
{
TempClass5 c55 = new TempClass5();
c55.UserList = new List<UserModel>(); c55.UserList.Add(new UserModel{ UserName="jiejiep", UserAge = });
c55.UserList.Add(new UserModel{ UserName="xiaoyi", UserAge = });
c55.UserList.Add(new UserModel{ UserName="zhangzetian", UserAge=}); TempClass8 c8 = new TempClass8();
c8.c5 = c55; WaitCallback callback = c8.ShowMessage; for(int c8.i=; c8.i < 3; c8.i++)
{
ThreadPool.QueueUserWorkItem(callback);
}
}
原来,编译器为我们生成了一个临时类,该类包含一个 int成员i,一个TempClass5实例c5, 一个实例方法 ShowMessage(object) 。再看看遍历部分的代码,我们顿时就豁然开朗了,原来一直都只有一个 TempClass8实例,遍历时始终改变的是tempCls对象的i字段的值。所以最后输出的,始终是最后一个遍历得到的元素的 UserName 。
我们再来看看使用临时变量后的代码,编译器是如何处理的呢。
public class Program
{
TempClass5 c55 = new TempClass5();
c55.UserList = new List<UserModel>(); c55.UserList.Add(new UserModel{ UserName="jiejiep", UserAge = });
c55.UserList.Add(new UserModel{ UserName="xiaoyi", UserAge = });
c55.UserList.Add(new UserModel{ UserName="zhangzetian", UserAge=}); for(int i=; i < 3; i++)
{
TempClass8 c8 = new TempClass8();
c8.c5 = c55;
c8.i = i;
WaitCallback callback = c8.ShowMessage;
ThreadPool.QueueUserWorkItem(callback);
}
}
我们看到,使用临时变量这种解决方案时,编译器相当于是每次遍历时都实例化了一个 TempClass8对象。所以内层函数引用的c8的i成员始终是遍历对应的元素。故能有效的解决闭包带来的陷阱。
写在文章后面:
这里感谢 dreamfor 的提醒。版本已经更正。
理解C#中的闭包的更多相关文章
- 浅谈 .NET 中的对象引用、非托管指针和托管指针 理解C#中的闭包
浅谈 .NET 中的对象引用.非托管指针和托管指针 目录 前言 一.对象引用 二.值传递和引用传递 三.初识托管指针和非托管指针 四.非托管指针 1.非托管指针不能指向对象引用 2.类成员指针 五 ...
- 两个函数彻底理解Lua中的闭包
本文通过两个函数彻底搞懂Lua中的闭包,相信看完这两个函数,应该能理解什么是Lua闭包.废话不多说,上 code: --[[************************************** ...
- 【译】理解Rust中的闭包
原文标题:Understanding Closures in Rust 原文链接:https://medium.com/swlh/understanding-closures-in-rust-21f2 ...
- 【原】理解javascript中的闭包
闭包在javascript来说是比较重要的概念,平时工作中也是用的比较多的一项技术.下来对其进行一个小小的总结 什么是闭包? 官方说法: 闭包是指有权访问另一个函数作用域中的变量的函数.创建闭包的常见 ...
- 【原】理解javascript中的闭包(***********************************************)
阅读目录 什么是闭包? 闭包的特性 闭包的作用: 闭包的代码示例 注意事项 总结 闭包在javascript来说是比较重要的概念,平时工作中也是用的比较多的一项技术.下来对其进行一个小小的总结 回到顶 ...
- 全面理解JavaScript中的闭包的含义及用法
1.什么是闭包 闭包:闭包就是能够读取其他函数内部变量的函数;闭包简单理解成“定义在一个函数内部的函数”. 闭包的形式:即内部函数能够使用它所在级别的外部函数的参数,属性或者内部函数等,并且能在包含它 ...
- 理解js中的闭包
闭包 我的理解是 能够有权访问另一个函数作用域中变量的函数 通常我们知道 普通的函数在调用完成之后,活动对象不会从内存中销毁,其执行环境的作用域链会被销毁,造成资源的浪费 而闭包的好处就在于执行完就会 ...
- 深入理解JavaScript中的闭包
闭包没有想象的那么简单 闭包的概念在JavaScript中占据了十分重要的地位,有不少开发者分不清匿名函数和闭包的概念,把它们混为一谈,我希望借这篇文章能够让大家对闭包有一个清晰的认识. 大家都知道变 ...
- java程序员理解js中的闭包
1.闭包概念: 就是函数内部通过某种方式访问一个函数内部的局部变量 再次理解: 闭包产生原因: 1.内部函数引用了外部函数的变量 作用:延长局部变量的生命周期 让函数外部可以调用到函数内部的数据 利用 ...
- 理解Python中的闭包
1.定义 闭包是函数式编程的一个重要的语法结构,函数式编程是一种编程范式 (而面向过程编程和面向对象编程也都是编程范式).在面向过程编程中,我们见到过函数(function):在面向对象编程中,我们见 ...
随机推荐
- Message
* Defines a message containing a description and arbitrary data object that can be* sent to a {@link ...
- 字符串匹配的Boyer-Moore算法 详解 加 C# 实现
上一篇文章,我介绍了KMP算法. 但是,它并不是效率最高的算法,实际采用并不多.各种文本编辑器的"查找"功能(Ctrl+F),大多采用Boyer-Moore算法. Boyer-Mo ...
- linux crontab介绍
第1列分钟1-59第2列小时1-23(0表示子夜)第3列日1-31第4列月1-12第5列星期0-6(0表示星期天)第6列要运行的命令 下面是crontab的格式:分 时 日 月 星期 要运行的命令 这 ...
- C++ 四种强制类型转换
来自csdn:http://blog.csdn.net/hgl868/article/details/46619399 C风格的强制类转换(Type Cast)很简单,不管什么类型的转换统统是: TY ...
- chrome 调试基本信息学习
学习链接: remote-debugging-port相关: http://blog.chromium.org/2011/05/remote-debugging-with-chrome-develop ...
- jquery 调用数据
<body> <div id="aa" style="">hello</div> <div class="b ...
- 文字编辑器kindeditor-min.js的使用
例子: <link rel="stylesheet" type="text/css" href="<?=$WebSiteRootDir?& ...
- IOS项目删除Git
默认创建工程会在MAC上面创建Git版本管理, 但是呢, 我现在想上传到svn服务器进行管理, 但是已经有个git 好像上传不了 只有把Git删了才能继续. 连问带查, 终于找到解决方案 把 .git ...
- asp.net 网站 或者web Api 发布
asp.net 发布iis时可能遇到的内部服务错误常见的有两种: 1.如下图,500.19 Internal Server Error(内部服务错误) 这种错误可能是由于本机的注册表中的asp.net ...
- HDOj 1010 DFS优化
#include<cstdio> #include<cstring> ]={,,,-}; ]={,,-,}; ][]; int x1,y1,x2,y2; int step; i ...