如何告诉lambda函数捕获副本而不是C#中的引用?

Ecl*_*pse 27 c# lambda loops capture

我一直在学习C#,我正在努力理解lambdas.在下面的示例中,它打印出10次.

class Program
{
    delegate void Action();
    static void Main(string[] args)
    {
        List<Action> actions = new List<Action>();

        for (int i = 0; i < 10; ++i )
            actions.Add(()=>Console.WriteLine(i));

        foreach (Action a in actions)
            a();
    }
}
Run Code Online (Sandbox Code Playgroud)

显然,lambda后面生成的类正在存储一个引用int i变量的引用或指针,并且每次循环迭代时都会为同一个引用分配一个新值.有没有办法强制lamda获取副本,比如C++ 0x语法

[&](){ ... } // Capture by reference
Run Code Online (Sandbox Code Playgroud)

[=](){ ... } // Capture copies
Run Code Online (Sandbox Code Playgroud)

Tin*_*ter 26

编译器正在做的是将lambda和lambda捕获的任何变量拉入编译器生成的嵌套类中.

编译后,您的示例看起来很像这样:

class Program
{
        delegate void Action();
        static void Main(string[] args)
        {
                List<Action> actions = new List<Action>();

                DisplayClass1 displayClass1 = new DisplayClass1();
                for (displayClass1.i = 0; displayClass1.i < 10; ++displayClass1.i )
                        actions.Add(new Action(displayClass1.Lambda));

                foreach (Action a in actions)
                        a();
        }

        class DisplayClass1
        {
                int i;
                void Lambda()
                {
                        Console.WriteLine(i);
                }
        }
}
Run Code Online (Sandbox Code Playgroud)

通过在for循环中创建副本,编译器在每次迭代中生成新对象,如下所示:

for (int i = 0; i < 10; ++i)
{
    DisplayClass1 displayClass1 = new DisplayClass1();
    displayClass1.i = i;
    actions.Add(new Action(displayClass1.Lambda));
}
Run Code Online (Sandbox Code Playgroud)

  • 无意冒犯作者,但这不应该是公认的答案。问题是关于强制 C# 按值捕获变量,这个答案仅解释了机制。我仍然不知道如何按值捕获,除非我使用“DisplayClass1”而不是 lambda(在我看来,这违背了目的)。 (3认同)

Ecl*_*pse 13

我能找到的唯一解决方案是首先制作本地副本:

for (int i = 0; i < 10; ++i)
{
    int copy = i;
    actions.Add(() => Console.WriteLine(copy));
}
Run Code Online (Sandbox Code Playgroud)

但是我很难理解为什么在for-loop中放一个副本与使用lambda捕获有什么不同i.

  • 因为int的声明在for循环中,所以每次都会重新创建它.有10个不同的int都命名为"copy",其中只有一个名为"i"的int,在curried的范围内. (15认同)

Jar*_*Par 8

唯一的解决方案是在lambda中制作本地副本和引用.在闭包中访问时,C#(和VB.Net)中的所有变量都将具有引用语义与复制/值语义.无法用任何一种语言更改此行为.

注意:它实际上并不编译为引用.编译器将变量提升到闭包类中,并将"i"的访问重定向到给定闭包类中的字段"i".然而,通常更容易将其视为参考语义.


Mat*_*ell 5

请记住,lambda 表达式实际上只是匿名方法的语法糖。

话虽这么说,您真正要寻找的是匿名方法如何在父作用域中使用局部变量。

这是描述这一点的链接。 http://www.codeproject.com/KB/cs/InsideAnonymousMethods.aspx#4