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

前言

在公众号上看到一篇文章《正确使用和理解C#中的闭包》,里面提到了闭包的一个坑:

当捕获的外部变量为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();
}

c1104dc19e87d0823a9854ba15f7e62e.png

那么,明明是循环体内定义的变量i,为什么会被认为定义在循环体外呢?

编译器魔法——Lowering

我们知道,C#代码最终会编译成IL中间语言。

假设有一个数组:

int[] arr = new[] { 0, 1, 2 };

我们可以有多种方式遍历它:

//1
foreach (var i1 in arr)
{i1.Dump();
}
//2
for (var i2 = 0; i2 < arr.Length; i2++)
{var value = arr[i2];value.Dump();
}
//3
var i3 = 0;
while(i3< arr.Length)
{var value = arr[i3];value.Dump();i3++;
}

那么,是不是要对应准备3种IL语法呢?

其实不是,在编译之前编译器还会施展一个魔法:Lowering

5000ff1e3f515d432b2af29fb600db64.png

大概意思是,让编译器从高级语言功能“降低”到同一语言中的低级语言功能。

怎么理解这句话呢?让我们打开https://sharplab.io/

Roslyn编译器实现

sharplab.io这个网站可以显示.NET代码(比如c#)的编译中间过程和结果。

我们将上面的C#代码复制到窗口左边:

a36aa517441a65f0b389673cb7d2de73.png

可以看到,编译器会将foreachfor语法都转换成while语法,这样,编译器最后只需要实现一种IL语法即可。

除了迭代以外,在roslyn编译器中实现了很多的“Lowering”,比如:

  • 异步重写器

  • Lambda重写器

  • 状态机重写器

详细列表你可以查看“https://github.com/dotnet/roslyn/tree/main/src/Compilers/CSharp/Portable/Lowering”下的代码。

结论

现在,大家应该已经知道,for循环中的变量i实际会被转换成while循环外定义的变量num,因此i在循环体作用域外也是有效的,导致了闭包的这个坑。

0c9cb3bd921facc96032fc62e5551700.png

知道了原理,解决方案也很简单,始终使用循环体内的变量即可:

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

如果你觉得这篇文章对你有所启发,请关注我的个人公众号”My IO“

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/296286.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C# 使用 HelpProvider 控件调用帮助文件

HelpProvider控件可以将帮助文件(.htm文件或.chm文件)与 Windows 应用程序相关联&#xff0c;为特定对话框或对话框中的特定控件提供区分上下文的帮助&#xff0c;打开帮助文件到特定部分。如目录、索引或搜索功能的主页。如图1 所示为 HelpProvider 控件。图1 HelpProvider…

哈哈哈,弟弟被卡桶里了......

1 哈哈哈哈哈弟弟被卡桶里面了&#xff08;via.小妮&#xff09;&#xff08;注意安全&#xff0c;请勿模仿&#xff01;&#xff09;▼2 弟弟是个狠人▼3 Self-potato: 一种不需要沙发也能无意义地待着的生活方式&#xff08;via.字幕少女&#xff09;▼4 防晒的正确打开方…

LVM基本应用 扩展及缩减实现

LVM: Logical Volume Manage首先&#xff1b;pv管理工具&#xff1a; pvs&#xff1a;简要pv信息显示 pvdisplay&#xff1a;显示pv的详细信息pvcreate /dev/DEVICE: 创建pvvg管理工具&#xff1a; vgs vgdisplayvgcreate [-s #[kKmMgGtTpPeE]] VolumeGroupName Physical…

java 线程 插件_我的第一个Chrome插件:天气预报应用

1.Chrome插件开发基础开发Chrome插件很简单&#xff0c;只要会基本的前台技术HTML、CSS、JS就可以开发了。Chrome插件一般包括两个HTML页面background和popup。background页面只在启动浏览器加载插件时载入一次&#xff0c;它不直接显示出来而是在后台运行。它包含了插件的主要…

保送北大,连发三篇Science,这位80后川妹子近日再发重磅级研究成果!

全世界只有3.14 % 的人关注了爆炸吧知识本文转自募格学术2020年9月21日&#xff0c;启函生物杨璐菡博士等在 Nature 子刊 Nature Biomedical Engineering杂志上发表了题为&#xff1a;Extensive germline genome engineering in pigs 的研究论文。杨璐菡杨璐菡带领的研究团队成…

Linq 下的 Take() 方法内部机制是怎样的?

咨询区 Rahul Kishore&#xff1a;我的web需要访问数据库&#xff0c;但是表比较大&#xff0c;我仅仅想要获取该表中 N 条数据&#xff0c;我查阅了 MSDN 文档&#xff0c;看到了一个 Take() 方法&#xff0c;我现在很疑惑它的运行机制是下面哪一种&#xff1f;先从数据库中获…

如何直接soap字符串,访问webservice

2019独角兽企业重金招聘Python工程师标准>>> 1.Webservice.GetVcardByUserNo(String userId&#xff0c;String userNo);这个是封装了的webservice接口。 2.在程序中连续两次调用该接口时&#xff0c;ksoap2在解析第二次调用返回的结果时抛异常。 异常信息如下&…

《哈利波特》电影全集+有声书免费领取!带你重返儿时魔法世界……

全世界只有3.14 % 的人关注了爆炸吧知识说到哈利波特系列&#xff0c;几乎人人皆知&#xff0c;享誉世界&#xff0c;风靡全球的哈利波特究竟有什么无穷魔力呢&#xff1f;《哈利波特》是英国作家JK罗琳的魔幻文学系列小说&#xff0c;共7集&#xff0c;其中前六部以霍格沃茨魔…

.NET6下周发布真的香,可不少人却只会.NET Framework!

倒计时7天&#xff0c;.NET6VS2022C#10将同时发布正式版&#xff0c;宣告.NET步入全新篇章&#xff0c;各种新语法、新框架、新技术都如约而至&#xff0c;令人期待&#xff01;近年来&#xff0c;.NET跨平台持续推出新版本&#xff0c;开源社区也不断涌现各种优秀框架&#xf…

java环境怎样搭建_如何学习JAVA?怎么搭建JAVA环境?怎么安装JDK?

JAVA在学习JAVA前&#xff0c;我们必须了解并搭建好JAVA所需的开发环境&#xff0c;要让你写代码能让机器听得懂并执行&#xff0c;JDK(Java Developers Kits)自然是是必须的安装JDK前的准备首先我们要先知道自己的电脑系统是几位版本的&#xff0c;右键”此电脑“点击菜单里的…

神奇的机械动态图,看了一遍又一遍!最后一个真神奇~

全世界只有3.14 % 的人关注了爆炸吧知识神奇的机械科技动态图&#xff0c;看了一遍又一遍&#xff01;最后一个真神奇&#xff5e;▲金属切割的慢镜头&#xff0c;美&#xff01;▲齿轮变速原理演示▲塑料成型机器▲切丝的食品机器▲螺旋状的通心粉制造▲高温融化锁的过程▲一次…

Envoy实现.NET架构的网关(三)代理GRPC

.NET网关与Gateway实战-Envoy与kong课程Envoy实现.NET架构的网关&#xff08;一&#xff09;静态配置与文件动态配置Envoy实现.NET架构的网关&#xff08;二&#xff09;基于控制平面的动态配置什么是GRPCgRPC是一种与语言无关的高性能远程过程调用 (RPC) 框架。gRPC 的主要好处…

Linux 下用来查询安装包信息的RPM选项

Linux 下用来查询安装包信息的RPM选项RPM是RedHat的包管理器&#xff0c;用来安装、卸载、升级和查询基于RedHat Linux的安装包。RHEL和基于它的系统使用rpm命令来完成这些功能。AD&#xff1a;RPM是RedHat的包管理器&#xff0c;用来安装、卸载、升级和查询基于RedHat Linux的…

如何用Java讲一句话重复五遍_Java 0515 第二次课作业

import java.util.*;public class Work_01 {public static void main(String[] args) {Scanner scan new Scanner(System.in);System.out.println("上午是否合格?");String judge scan.next();//输入是否while(!"是".equals(judge)){//用equals方法判断…

一张图看懂华为计算全联接2020

全世界只有3.14 % 的人关注了爆炸吧知识END◆ 推荐阅读 ◆点击下方图片即可阅读华为邓泰华&#xff1a;让每一位开发者的智慧汇聚成全生态创新&#xff0c;共同点亮多样性计算新时代左右滑动查看更多☟

独立开发一个云(PaaS)的核心要素, Go, Go, Go!!!

最近一年的工作&#xff0c;有很大的比重在做云平台的事情&#xff0c;简单来说&#xff0c;就是为公司内用户提供一个PaaS&#xff0c;用户可以在我们的云平台上方便的将单机服务程序扩展为多实例程序&#xff0c;以平台服务化的方式对外提供。在这里简单分享一下。 首先简单说…

C# 使用Timer控件设置时间间隔

Timer 控件可以定期引发事件&#xff0c;该控件是为 Windows 窗体环境设计的。时间间隔的长度由 Interval 属性定义&#xff0c;其值以毫秒为单位。若启用了该组件&#xff0c;则每个时间间隔引发一个 Tick 事件&#xff0c;在该事件中添加要执行的代码。如图1 所示为 Timer 控…

吐血整理!近二十年全国数学联赛赛题大全,烧脑全集来啦!

1981年&#xff0c;中国数学会开始举办"全国数学联赛"&#xff0c;经过1981、1982、1983三年的实践&#xff0c;这一群众性的数学竞赛活动得到了广大中学师生欢迎&#xff0c;也得到教育行政部门、各级科学技术协会、以及社会各阶层人士的肯定和支持。"试题所涉…

组装电脑教程(转载)

组装电脑已经越来越受欢迎&#xff0c;性价比在那摆着&#xff0c;而之前品牌机引以为傲的售后服务也已经被组装机赶上&#xff0c;可以说已经没有不选组装机的理由&#xff0c;就连王思聪也是买组装电脑&#xff08;几万元土豪组装机&#xff09;。但是组装电脑对于新手却不是…

java基本类型与引用数据类型_java基本数据类型与引用数据类型总结

昨天京东笔试的一道选择题&#xff0c;被坑了。最近一直在做笔试题&#xff0c;发现自己学了两年多的java&#xff0c;三大框架什么都会&#xff0c;却连基本的拆箱装箱都忘了。昨天考的引用数据类型&#xff0c;基本数据类型都不知道。看来得好好把基础复习几遍了。总结&#…