定义

我们把在Lambda表达式(或匿名方法)中所引用的外部变量称为捕获变量。而捕获变量的表达式就称为闭包

捕获变量

捕获的变量会在真正调用委托时“赋值”,而不是在捕获时“赋值”,即总是使用捕获变量的最新的值。如下代码所示,调用委托时,age的最新值为30,所以输出的值也是30。

int age = 28;
//定义委托
Func<int, string> consoleAge = i => $"洋小豆今年{i}岁了";
age = 30;
//调用委托
string outputMsg = consoleAge(age);
outputMsg.Dump();

输出结果如下图:

捕获迭代变量

当捕获的外部变量为for循环的迭代变量时,C#认为变量i是定义在循环体外的。所以,当添加委托集合的for循环执行完时,i的值已经变为3了;因此,我们在foreach中循环调用委托时,i的值就都是3了。

List<Action> levyActions = new List<Action>();
for (int i = 0; i < 3; i++)
{levyActions.Add(()=> i.Dump());
}
foreach (Action action in levyActions)
{action();
}

输出结果如下图:

那么,如果我们期望输出的结果为1,2,3那需要怎么修改呢?这里我们只需要在for循环内部使用局部变量即可(每次循环捕获的是不同的变量),如下修改后的代码:

for (int i = 0; i < 3; i++)
{int tmp = i;levyActions.Add(()=> tmp.Dump());
}

输出结果如下图:

看到这里大家应该基本明白怎么回事了吧!再想下,迭代除了可以使用for还可以使用foreach啊!那么,我们把上面示例中的for循环部分改造成foreach会怎么样呢?

string[] names = new string[] {"洋小豆", "列位一分钟", "levy"};
List<Action> levyActions = new List<Action>();
foreach (string name in names)
{levyActions.Add(()=> name.Dump());
}

输出结果如下图:

纳尼?输出的结果竟然跟我们上面的讲解不一样?不是应该输出捕获变量的最新值吗?应该输出3个“levy”啊!哈哈,这里是因为我的示例代码是基于.net core3.0的,从C#5.0开始,foreach认为循环变量都应该是“新”的变量。所以,每次循环中创建委托时捕获的变量都不是同一个变量。因而,输出的值肯定也就不一样了。有兴趣的童鞋可以在C#5.0之前的版本下测试下,看看输出的是不是3个“levy”。

背后原理

分析IL代码我们可以得知,编译器在背后生成了一个私有的密封类c__DisplayClass4_0,它将外部变量包装成类的成员变量,而委托方法包装成类的方法。所以,上述捕获for迭代变量的示例代码就可以修改成如下:

void Main()
{List<Action> levyActions = new List<Action>();c__DisplayClass4_0 local = new c__DisplayClass4_0();for (local.i = 0; local.i < 3; local.i++){levyActions.Add(() => local.Main_b__0());}foreach (Action action in levyActions){action();}
}private sealed class c__DisplayClass4_0
{public int i;internal void Main_b__0(){i.Dump();}
}

涉及到外部变量i的地方都替换成了local.i,所以,每次循环修改的始终是c__DisplayClass4_0的成员变量i的值,那么调用委托时输出的自然都是3了。

正确使用和理解C#中的闭包相关推荐

  1. 从编译器层面理解C#中的闭包的这个坑!

    前言 在公众号上看到一篇文章<正确使用和理解C#中的闭包>,里面提到了闭包的一个坑: 当捕获的外部变量为for循环的迭代变量时,C#认为变量i是定义在循环体外的.所以,当添加委托集合的fo ...

  2. 两个函数彻底理解Lua中的闭包

    本文通过两个函数彻底搞懂Lua中的闭包,相信看完这两个函数,应该能理解什么是Lua闭包.废话不多说,上 code: 1 --[[************************************ ...

  3. 深入理解JavaScript中的闭包

    闭包没有想象的那么简单 闭包的概念在JavaScript中占据了十分重要的地位,有不少开发者分不清匿名函数和闭包的概念,把它们混为一谈,我希望借这篇文章能够让大家对闭包有一个清晰的认识. 大家都知道变 ...

  4. 十分钟,快速理解JavaScript中的闭包概念

    海阔凭鱼跃,天高任鸟飞.Hey 你好!我是猫力Molly 闭包已经是一个老生常谈的问题了,不同的人对闭包有不同的理解.今天我来浅谈一下闭包,大家一起来就"闭包"这个话题,展开讨论, ...

  5. python闭包与装饰器有啥关系_深入理解Python中的闭包与装饰器

    函数的装饰器可以以某种方式增强函数的功能,如在 Flask 中可使用 @app.route('/') 为视图函数添加路由,是一种十分强大的功能.在表现形式上,函数装饰器为一种嵌套函数,这其中会涉及到闭 ...

  6. python闭包和装饰器的区别_深入理解Python中的闭包与装饰器

    函数的装饰器可以以某种方式增强函数的功能,如在 Flask 中可使用 @app.route('/') 为视图函数添加路由,是一种十分强大的功能.在表现形式上,函数装饰器为一种嵌套函数,这其中会涉及到闭 ...

  7. python装饰器作用-理解python中的装饰器

    一 什么是装饰器? 正如其名,装饰器的作用是为已经存在的对象增加额外功能(装饰),由此可使已有函数在无需代码改动的情况下增加额外功能:装饰器的本质是嵌套的函数且返回函数对象,即闭包.有关闭包的概念,可 ...

  8. [译]Javascript中的闭包(closures)

    本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ...

  9. 正确理解WPF中的TemplatedParent

    原文:正确理解WPF中的TemplatedParent http://www.cnblogs.com/mgen/archive/2011/08/31/2160581.html (注:Logical T ...

最新文章

  1. 分享Kali Linux 2017年第31周镜像文件
  2. linux项目变量存放,linux 堆、栈、全局变量存放
  3. 华为被上汽狂DISS:智能驾驶时代车企呼唤“主权”?
  4. 娱美德投入千万研发 揭秘韩国第一游戏引擎
  5. why debug log could not be displayed in console
  6. C# JSON格式数据用法
  7. Silverlight带关闭动画的内容控件,可移动的内容控件(一)
  8. 拉氏变换法求解线性常微分方程(系统的零状态响应)
  9. java sql in无效数字_java.sql.SQLSyntaxErrorException: ORA-01722: 无效数字
  10. MacBook配置快捷轻量的C/C++环境
  11. Flex与.NET互操作(十五):使用FluorineFx中的字节数组(ByteArray)实现图片上传
  12. 阿里云送你Hands-on Labs X linux联名T恤——阿里云高校计划《Linux命令入门》训练营
  13. TestStand自定义报表生成-添加列
  14. js跳转到新标签打开PDF文件
  15. android课程设计健身,健身软件课程设计-毕业论文.doc
  16. 许又声发表新春贺辞:潮涌东方 携手奋进
  17. 用labview设计jk触发器_编写数字电路JK触发器仿真程序
  18. 右键菜单“发送到”的修改
  19. POJ 1950 Dessert
  20. 鸿海集团董事长郭台铭:数字经济是中国制造2025的根基

热门文章

  1. 想了好久 请多多指教
  2. Multiverse: Revolutionary Backend for Alembic // Multiverse: 下一代Alembic后端
  3. [Docker]记一次使用jenkins将镜像文件推送到Harbor遇到的问题
  4. 自动调试自动编译五分钟上手
  5. 算法导论--广度优先搜索和深度优先搜索
  6. CSV格式数据如何导入MySQL?
  7. 总结 一下UML 类图的关系
  8. vue-router query,parmas,meta传参
  9. pta l2-6(树的遍历)
  10. dubbo源码解析-zookeeper创建节点