gpt4 book ai didi

你认识的C#foreach语法糖,真的是全部吗?

转载 作者:我是一只小鸟 更新时间:2022-11-21 14:37:37 26 4
gpt4 key购买 nike

本文的知识点其实由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
    }
}

                        
                      

👇 foreach官方信源 。

请注意注释,变量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语法糖,真的是全部吗?的文章就讲到这里了,如果你想了解更多关于你认识的C#foreach语法糖,真的是全部吗?的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com