你认识的C# foreach语法糖,真的是全部吗?
本文的知识点其实由golang知名的for循环陷阱发散而来,
对应到我的主力语言C#, 其实牵涉到闭包、foreach。为了便于理解,我重新组织了语言,以倒叙结构行文。
先给大家提炼出一个C#题:观察for、foreach闭包的差异

左边输出 5个5; 右边输出0,1,2,3,4, 答对的可以不用看下文了。
闭包是在词法环境中捕获自由变量的头等函数, 题中关键是捕获的自由变量。
这里面有3个关键名词,希望大家重视,可以围观我之前的新来的总监,把C#闭包讲得那叫一个透彻。
demo1
- for循环内闭包,局部变量i是被头等函数引用的自由变量;相对于每个头等函数,i是全局变量;
- 闭包捕获变量i的时空和 闭包执行的时空不是一个时空;
- 所有闭包执行时,捕获的都是变量i,所以执行输出的都是
i++最后的5。
这也是C#闭包的陷阱, 通常应对方式是循环内使用一个局部变量解构每个闭包与(相对全局)变量i的关系。
var t1 = new List<Action>();
for (int i = 0; i < 5; i++)
{
// 使用局部变量解绑闭包与全局自由变量i的关系,现在自由变量是局部变量j了。
var j = i;
var func = (() =>
{
Console.WriteLine(j);
});
t1.Add(func);
}
foreach (var item in t1)
{
item();
}
demo2
foreach内闭包,为什么能输出预期的0,1,2,3,4。
聪明的读者可以猜想,是不是foreach在循环迭代时 ,给我们搞出了局部变量j,帮我们解构了闭包与全局自由变量i多对1的关系。
foreach的底层实现有赖于IEnumerable和IEnumerator两个接口的实现、
这里也有一个永久更新的原创文,IEnumerator、IEnumerable还傻傻分不清楚?
但是怎么用这个两个接口,还需要看foreach伪代码:
C# foreach foreach (V v in x) «embedded_statement»被翻译成下面代码。
{
E e = ((C)(x)).GetEnumerator();
try
{
while (e.MoveNext())
{
V v = (V)(T)e.Current; // 注意这句, 变量v的定义是在循环体内
«embedded_statement»
}
}
finally
{
... // Dispose e
}
}
请注意注释,变量v的定义是在循环内部, 因此使用foreach迭代时,每个闭包捕获的都是局部的自由变量, 因此foreach闭包执行时输出0,1,2,3,4。
如果变量V v定义在while语言上方,那么效果就和for循环一样了。
这是for循环/foreach迭代一个很有意思的差异。
以上理解透彻之后,我们再看Golang的for循环陷阱, 也就很容易理解了。
package main
import "fmt"
var slice []func()
func main() {
sli := []int{1, 2, 3, 4, 5}
for _, v := range sli {
fmt.Println(&v, v)
slice = append(slice, func() {
fmt.Println(v)
})
}
for _, val := range slice {
val()
}
}
--- output ---
0xc00001c098 1
0xc00001c098 2
0xc00001c098 3
0xc00001c098 4
0xc00001c098 5
5
5
5
5
5
golang for循环的使用姿势类似于C#的 foreach, 但是内核却是for循环。
每个闭包引用的都是(相对全局的)自由变量v,最终闭包执行的是同一个变量。
应对这种陷阱的思路,依旧是使用循环内局部变量去解构闭包与相对全局变量v的关系。
另外 闭包 foreach 还能与多线程结合,又有不一样的现象。
画外音
本文其实内容很多:
- 闭包:是在词法环境中捕获自由变量的头等函数
- foreach 语法糖:依赖于IEnumerable和IEnumerator 接口实现,同时 foreach每次迭代使用的是块内局部变量, for循环变量是相对的全局变量, 也正是这个差异,导致了投票题的结果。
每一个知识点都是比较重要且晦涩难懂,篇幅有限,请适时关注文中给出的几个永久更新地址。
你认识的C# foreach语法糖,真的是全部吗?的更多相关文章
- 迭代器Iterator与语法糖for-each
一.为什么需要迭代器 设计模式迭代器 迭代器作用于集合,是用来遍历集合元素的对象.迭代器迭代器不是Java独有的,大部分高级语言都提供了迭代器来遍历集合.实际上,迭代器是一种设计模式: 迭代器模式提供 ...
- Java语法糖1:可变长度参数以及foreach循环原理
语法糖 接下来几篇文章要开启一个Java语法糖系列,所以首先讲讲什么是语法糖.语法糖是一种几乎每种语言或多或少都提供过的一些方便程序员开发代码的语法,它只是编译器实现的一些小把戏罢了,编译期间以特定的 ...
- java语法糖:(1)可变长度参数以及foreach循环原理
语法糖 语法糖:是一种几乎每种语言或多或少都提供过的一些方便程序员开发代码的语法,它只是编译器实现的一些小把戏罢了,编译期间以特定的字节码或者特定的方式对这些语法做一些处理,开发者就可以直接方便地使用 ...
- Hollis原创|不了解这12个语法糖,别说你会Java
GitHub 2.5k Star 的Java工程师成神之路 ,不来了解一下吗? GitHub 2.5k Star 的Java工程师成神之路 ,真的不来了解一下吗? GitHub 2.5k Star 的 ...
- C#语法糖系列 —— 第三篇:聊聊闭包的底层玩法
有朋友好奇为什么将 闭包 归于语法糖,这里简单声明下,C# 中的所有闭包最终都会归结于 类 和 方法,为什么这么说,因为 C# 的基因就已经决定了,如果大家了解 CLR 的话应该知道, C#中的类最终 ...
- C#语法糖大汇总
首先需要声明的是"语法糖"这个词绝非贬义词,它可以给我带来方便,是一种便捷的写法,编译器会帮我们做转换:而且可以提高开发编码的效率,在性能上也不会带来损失.这让java开发人员羡慕 ...
- 看看C# 6.0中那些语法糖都干了些什么(终结篇)
终于写到终结篇了,整个人像在梦游一样,说完这一篇我得继续写我的js系列啦. 一:带索引的对象初始化器 还是按照江湖老规矩,先扒开看看到底是个什么玩意. 1 static void Main(strin ...
- 看看C# 6.0中那些语法糖都干了些什么(上篇)
今天没事,就下了个vs2015 preview,前段时间园子里面也在热炒这些新的语法糖,这里我们就来看看到底都会生成些什么样的IL? 一:自动初始化属性 确实这个比之前的版本简化了一下,不过你肯定很好 ...
- C#语法糖,让编程更具乐趣
一.什么是语法糖 语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法 ...
- C#语法糖(Csharp Syntactic sugar)大汇总
首先需要声明的是"语法糖"这个词绝非贬义词,它可以给我带来方便,是一种便捷的写法,编译器会帮我们做转换:而且可以提高开发编码的效率,在性能上也不会带来损失.这让java开发人员羡慕 ...
随机推荐
- KingbaseES 表空间加密-透明加密
透明存储加密是指数据在写到磁盘上时对其进行加密,当授权用户重新读取数据时再对其进行解密.加密解密过程对用户都是透明的,无需对应用程序进行修改,授权用户甚至不会注意到数据已经在存储介质上加密.当前的透明 ...
- Spark 写 Hbase
package com.grady import org.apache.hadoop.hbase.HBaseConfiguration import org.apache.hadoop.hbase.c ...
- SpringBoot使用自定义注解+AOP+Redis实现接口限流
为什么要限流 系统在设计的时候,我们会有一个系统的预估容量,长时间超过系统能承受的TPS/QPS阈值,系统有可能会被压垮,最终导致整个服务不可用.为了避免这种情况,我们就需要对接口请求进行限流. 所以 ...
- MariaDB数据库 主-从 部署
〇.前言 好久没碰数据库了 准备环境: centos7自带的MariaDB,没有的话下面是安装命令 yum install -y mariadb mariadb-server systemctl re ...
- LFS(Linux From Scratch)构建过程全记录(三):下载所需的软件包
写在前面 本文将记录构建LFS的过程中,下载软件包的全过程 准备下载的路径 注意请确保$LFS已经设置完毕 我们需要创建一个文件夹,地址为$LFS/sources,用于保存对应的源码 输入的指令如下: ...
- 《吐血整理》进阶系列教程-拿捏Fiddler抓包教程(20)-Fiddler精选插件扩展安装,让你的Fiddler开挂到你怀疑人生
1.简介 Fiddler本身的功能其实也已经很强大了,但是Fiddler官方还有很多其他扩展插件功能,可以更好地辅助Fiddler去帮助用户去开发.测试和管理项目上的任务.Fiddler已有的功能已经 ...
- 使用kubeoperator安装的k8s 版本1.20.14 将节点上的容器运行时从 Docker Engine 改为 containerd
官方文档:https://kubernetes.io/zh-cn/docs/tasks/administer-cluster/migrating-from-dockershim/change-runt ...
- 查询参数: Query Parameters
官方文档地址: https://fastapi.tiangolo.com/zh/tutorial/query-params/ # -*- coding: UTF-8 -*- from fastapi ...
- Alermanager_template,email
default.tmpl {{ define "__subject" }}[{{ .Status | toUpper }}{{ if eq .Status "firing ...
- 使用cnpm创建vue项目(含离线安装)
# 全局安装淘宝cnpm npm install -g cnpm --registry=https://registry.npm.taobao.org #升级 npm cnpm install npm ...