跳至主要內容
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?

4563博客

全新的繁體中文 WordPress 網站
  • 首頁
  • 一个感觉难以理解的的 C#代码片段,想知道这是为什么
未分類
3 10 月 2020

一个感觉难以理解的的 C#代码片段,想知道这是为什么

一个感觉难以理解的的 C#代码片段,想知道这是为什么

資深大佬 : richards64 4

代码如下:

Action[] foos = new Action[4]; for (int i = 0; i < 4; i++) {     funcs[i] = () => Console.Write($"{i} "); } foreach (var foo in foos) {     foo(); } 

输出如下:

4 4 4 4 

为什么输出不是0 1 2 3而是4 4 4 4呢

大佬有話說 (20)

  • 資深大佬 : Soar360

    闭包

  • 資深大佬 : across

    我记得 c # via cia 好像解释过,这种函数的实质是创建一个类,引用 i 变量。因循环结束,引用的 i 值就变成 4 了…

  • 資深大佬 : mengqi

    比较常见的因为惰性求值导致的问题。因为 Console.Write(…) 不是在 for (int i…) 循环内求值,而是延迟到了 foreach 里面求值,而在那时变量 i 值为 4,因此 foreach 打印了 4 遍 i 就是 “4 4 4 4” 了

  • 資深大佬 : crella

    不懂 c#,感觉是 Console.Write()里并没有保存 i 的值,仅保存了对其的引用

  • 資深大佬 : crclz

    闭包的知识点,详细的可以参考 js

  • 資深大佬 : YenvY

    四个 lambda 捕获了同一个 i

  • 主 資深大佬 : richards64

    @across 能大概说下在哪个章节吗?我翻翻看

  • 資深大佬 : lxilu

    [CompilerGenerated]
    private sealed class a_0
    {
    public int i;

    internal void <M>b__0()
    {
    Console.Write(string.Format(“{0} “, i));
    }
    }

    public void M()
    {
    Action[] array = new Action[4];
    a_0 a_ = new a_0();
    a_.i = 0;
    while (a_.i < 4)
    {
    array[a_.i] = new Action(a_.<M>b__0);
    a_.i++;
    }
    Action[] array2 = array;
    for (int i = 0; i < array2.Length; i++)
    {
    array2[i]();
    }
    }

    注:为便于观看有改名;编译器实现,准确见标准;来自 sharplab.io ;

  • 資深大佬 : hejingyuan199

    这个我好像觉得很自然就是 4 4 4 4 。
    但我不知道如何解释。
    因为你在后面才调用的 foo,那时候 i 已经是 4 了。
    那当然是打印出全是 4 啊。

    我就排个队,看看各位如何解释。

  • 資深大佬 : lights

    看具体的语言实现吧,这个是纯的硬性知识点了,只是刚好 C#里实现的是 8 所说的形式

    不过好奇为啥是这种实现方式(四个 lambda 只 new 一个类)而不是 new 4 个类。有大佬解释一下吗?

  • 資深大佬 : lights

    @across #2 有介绍这种设计的原因吗?四个 labmda 只 new 一个类,不应该是 new 4 个类吗?感觉挺反直觉的

  • 資深大佬 : youla

    Action[] foos = new Action[4];
    for (int i = 0; i < 4; i++)
    {
    foos[i] = () => Console.Write(i);
    foos[i].Invoke();
    }

    //0 1 2 3

  • 資深大佬 : zhuang0718

    lambda 表达式的原因?

  • 資深大佬 : geelaw

    @lights #11 显然这里一共 new 了 5 个东西,一个是提供状态的对象(即编译器生成的),接下来每次赋值给委托的时候都 new 了一个 Action 。每个 Action 都引用了同一个状态对象的同一个方法,至于为什么同一个状态对象,这是因为 i 的 scope 只进入了一次。这里的 for 循环等价于

    for (init; cond; next) body;

    { // 1
    init;
    while (cond)
    { // 2
    body;
    goto_continue:
    next;
    } // 2
    } // 1

    循环变量 i 的 scope 是 1,而 scope 1 只进入了一次,所以只有一个状态对象。

    如果你改成

    foreach (int i in new int[] { 0,1,2,3 })

    则在较新的 C# 编译器下会得到 0 1 2 3,因为这等价于

    { // 1
    var coll = new int[] { 0,1,2,3 };
    for (int idx = 0; idx < coll.Length; ++idx)
    { // 2
    int i = coll[idx];
    foos[i] = () => Console.WriteLine(i);
    } // 2
    } // 1

    因为 i 的 scope 变成了 for 的里面,所以会进入 4 次,因此有 4 个不同的状态对象被创建,每个 Action 都会引用各自状态对象的方法。

  • 資深大佬 : lxilu

    ECMA-334
    5th Edition/December 2017
    C# Language Specification

    12. Expressions
    12.16 Anonymous function expressions
    12.16.6 Outer variables

    If a for-loop declares an iteration variable, that variable itself is considered to be declared outside of the loop.
    => i 在 for 外
    Any local variable, value parameter, or parameter array whose scope includes the lambda-expression or anonymous-method-expression is called an outer variable of the anonymous function.
    => i 也在匿名函数外
    A local variable is considered to be instantiated when execution enters the scope of the variable.
    => i 仅一实例
    When an outer variable is referenced by an anonymous function, the outer variable is said to have been captured by the anonymous function.
    => 匿名函数用了那唯一 i

    标准里的例子:
    ░░static D[] F() {
    ░░░░D[] result = new D[3];
    ░░░░for (int i = 0; i < 3; i++) {
    ░░░░░░result[i] = () => { Console.WriteLine(i); };
    ░░░░}
    ░░░░return result;
    ░░}
    only one instance of the iteration variable is captured, which produces the output:
    ░░3
    ░░3
    ░░3

  • 資深大佬 : mway

    跟变量的作用域有关系,i 是整个 for 循环体的局部变量,相当于给匿名委托引用了同一个函数,可以先将 i 复制给单次循环内的临时变量:
    int n= i;
    foos[i] = () => Console.Write($”{n} “);
    或者将 Lambda 转为有参的,foos[i] = n => Console.Write($”{n} “);调用时再把 i 传入。
    还可以用多播委托,
    这样打印出来的就是 0123 了。

  • 資深大佬 : Youen

    这么写 Resharper 会骂的…

    https://www.jetbrains.com/help/resharper/AccessToModifiedClosure.html

  • 資深大佬 : dartabe

    从 javascript 来看 变量作用域的原因 所有的 i 变量都是顶层作用域的 i

    等价于

    Action[] foos = new Action[4];

    int I;

    for (i = 0; i < 4; i++)
    {
    funcs[i] = () => Console.Write($”{i} “);
    }

  • 資深大佬 : laminux29

    请问题主是遇到啥需求,写了这样一段奇怪的代码?

  • 資深大佬 : xuanbg

    因为真正执行 Console.Write($”{i} “);不是在 for 循环里面,而是在 foreach 迭代里面呀。

文章導覽

上一篇文章
下一篇文章

AD

其他操作

  • 登入
  • 訂閱網站內容的資訊提供
  • 訂閱留言的資訊提供
  • WordPress.org 台灣繁體中文

51la

4563博客

全新的繁體中文 WordPress 網站
返回頂端
本站採用 WordPress 建置 | 佈景主題採用 GretaThemes 所設計的 Memory
4563博客
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?
在這裡新增小工具