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

本文的知识点其实由golang知名的for循环陷阱发散而来,
对应到我的主力语言C#, 其实牵涉到闭包、foreach。为了便于理解,我重新组织了语言,以倒叙结构行文。

先给大家提炼出一个C#题:观察for、foreach闭包的差异

你认识的C# 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++)
        {
         // &#x4F7F;&#x7528;&#x5C40;&#x90E8;&#x53D8;&#x91CF;&#x89E3;&#x7ED1;&#x95ED;&#x5305;&#x4E0E;&#x5168;&#x5C40;&#x81EA;&#x7531;&#x53D8;&#x91CF;i&#x7684;&#x5173;&#x7CFB;,&#x73B0;&#x5728;&#x81EA;&#x7531;&#x53D8;&#x91CF;&#x662F;&#x5C40;&#x90E8;&#x53D8;&#x91CF;j&#x4E86;&#x3002;
            var j = i;
            var func = (() =>
            {
                Console.WriteLine(j);
            });
            t1.Add(func);
        }
        foreach (var item in t1)
        {
            item();
        }
</action>

demo2

foreach内闭包,为什么能输出预期的0,1,2,3,4。

聪明的读者可以猜想,是不是foreach在循环迭代时 ,给我们搞出了局部变量j,帮我们解构了闭包与全局自由变量i多对1的关系。

foreach的底层实现有赖于 IEnumerableIEnumerator两个接口的实现、

这里也有一个永久更新的原创文,👇IEnumerator、IEnumerable还傻傻分不清楚?

但是怎么用这个两个接口,还需要看foreach伪代码:
C# 👇 foreach官方信源
foreach (V v in x) &#xAB;embedded_statement&#xBB;被翻译成下面代码。

{
    E e = ((C)(x)).GetEnumerator();
    try
    {
        while (e.MoveNext())
        {
            V v = (V)(T)e.Current; // &#x6CE8;&#x610F;&#x8FD9;&#x53E5;&#xFF0C; &#x53D8;&#x91CF;v&#x7684;&#x5B9A;&#x4E49;&#x662F;&#x5728;&#x5FAA;&#x73AF;&#x4F53;&#x5185;
            &#xAB;embedded_statement&#xBB;
        }
    }
    finally
    {
        ... // Dispose e
    }
}

变量v的位置对于怎样捕获变量v是很重要的。

int[] values = { 7, 9, 13 };
Action f = null;
foreach (var value in values)
{
    if (f == null)
    {
        f = () => Console.WriteLine("First value: " + value);
    }
}
f();

If v in the expanded form were declared outside of the while loop, it would be shared among all iterations, and its value after the for loop would be the final value, 13, which is what the invocation of f would print. Instead, because each iteration has its own variable v, the one captured by f in the first iteration will continue to hold the value 7, which is what will be printed. (Note that earlier versions of C# declared v outside of the while loop.)

这是for循环/foreach迭代一个很有意思的差异。

泛泛而言

&#x5FAA;&#x73AF;&#x8FED;&#x4EE3;这两个术语到底该怎么理解? 在各大语言的实现机制是怎样的 ?

循环: 满足某种条件, 而重复执行一段代码, 上面的demo1输出5个5, 是因为重复执行的代码公用了同一个全局自由变量。

迭代: 按顺序去访问一个列表中的每一项,C# 早期迭代变量也是作为公共变量, 现在是作为循环的内部变量, 于是形成了与for循环的差异。

以上理解透彻之后,我们再看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()
}

}

Original: https://www.cnblogs.com/JulianHuang/p/16907679.html
Author: 博客猿马甲哥
Title: 你认识的C# foreach语法糖,真的是全部吗?

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/798194/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球