理解c#中的闭包
来源:互联网 发布:迈网络摄像头默认ip 编辑:程序博客网 时间:2024/06/06 12:42
引用:
http://www.cnblogs.com/jiejie_peng/p/3701070.html
闭包的概念
内层的函数可以引用包含在它外层的函数的变量,即使外层函数的执行已经终止。但该变量提供的值并非变量创建时的值,而是在父函数范围内的最终值。
闭包的优点
使用闭包,我们可以轻松的访问外层函数定义的变量,这在匿名方法中普遍使用。比如有如下场景,在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 = 26}, new UserModel{ UserName="xiaoyi", UserAge = 25}, new UserModel{ UserName="zhangzetian", UserAge=24} }; for(int i = 0 ; i < 3; i++) { ThreadPool.QueueUserWorkItem((obj) => { //TODO //Do some process... //... Thread.Sleep(1000); 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 = 26}, new UserModel{ UserName="xiaoyi", UserAge = 25}, new UserModel{ UserName="zhangzetian", UserAge=24} }; for(int i = 0 ; i < 3; i++) { UserModel u = userList[i]; ThreadPool.QueueUserWorkItem((obj) => { //TODO //Do some process... //... Thread.Sleep(1000); //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 = 0; public TempClass5 c5; public ShowMessage(object o) { Thread.Sleep(1000); 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 = 26}); c55.UserList.Add(new UserModel{ UserName="xiaoyi", UserAge = 25}); c55.UserList.Add(new UserModel{ UserName="zhangzetian", UserAge=24}); TempClass8 c8 = new TempClass8(); c8.c5 = c55; WaitCallback callback = c8.ShowMessage; for(int c8.i=0; 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 = 26}); c55.UserList.Add(new UserModel{ UserName="xiaoyi", UserAge = 25}); c55.UserList.Add(new UserModel{ UserName="zhangzetian", UserAge=24}); for(int i=0; i < 3; i++) {
TempClass8 c8 = new TempClass8(); c8.c5 = c55; c8.i = i;
WaitCallback callback = c8.ShowMessage;
ThreadPool.QueueUserWorkItem(callback); } }
- 理解c#中的闭包
- 理解js中的闭包
- Lua中的闭包理解
- 理解JavaScript中的闭包
- 理解js中的闭包
- 理解JavaScript中的闭包
- 理解js中的闭包
- 理解js中的闭包
- python中的闭包理解
- 理解JavaScript中的闭包
- 理解javascript中的闭包
- 深入理解JavaScript中的闭包
- 深入理解javascript中的闭包
- 带你理解JavaScript中的闭包
- 深入理解什么是javascript中的闭包
- 深入理解js中的闭包
- 对Javascript中的闭包的理解
- js中的闭包之我理解
- Hadoop 及 Hive 压缩应用
- 动脑学院笔记
- jQuery基础
- 归并排序--JAVA代码实现
- SWOT分析和PEST分析
- 理解c#中的闭包
- 洛谷P1141 01迷宫
- Ubuntu14.04安装CPU版SSD(Single Shot MultiBox Detector)/Caffe版本(一)
- corejava_OOP
- 学习笔记| AS入门(五) 高级控件篇(中)
- hbase集群安装配置
- Unity18--鼠标和场景相反运动、物体绕过障碍物,向目标自动移动,且鼠标点击哪里,物体也运动
- 一年总结
- Ubuntu14.04安装CPU版SSD(Single Shot MultiBox Detector)/Caffe版本(二)