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

Posted

技术标签:

【中文标题】如何告诉 lambda 函数捕获副本而不是 C# 中的引用?【英文标题】:How to tell a lambda function to capture a copy instead of a reference in C#? 【发布时间】:2010-10-01 21:28:01 【问题描述】:

我一直在学习 C#,并且正在尝试理解 lambda。在下面的示例中,它打印了 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();
    

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

[&]() ...  // Capture by reference

对比

[=]() ...  // Capture copies

【问题讨论】:

您可能想阅读this article,由我们自己的 Jon Skeet 撰写。 C# Captured Variable In Loop的可能重复 我觉得奇怪的是,这个问题的大多数答案都在解释捕获语义,这对问题的作者来说是非常清楚的,而只有一些人提到了解决方案(临时副本)。没有人在回答之前阅读问题吗? 【参考方案1】:

编译器所做的是将您的 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);
                
        

通过在 for 循环中进行复制,编译器会在每次迭代中生成新对象,如下所示:

for (int i = 0; i < 10; ++i)

    DisplayClass1 displayClass1 = new DisplayClass1();
    displayClass1.i = i;
    actions.Add(new Action(displayClass1.Lambda));

【讨论】:

谢谢。这对于弄清楚表达式尝试评估参数时发生的情况非常有帮助。 无意冒犯作者,但这不应该是公认的答案。问题是关于强制 C# 按值捕获变量,这个答案只解释了机制。我仍然不知道如何按值捕获,除非我使用 DisplayClass1 而不是 lambda(IMO 违背了目的)。【参考方案2】:

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

for (int i = 0; i < 10; ++i)

    int copy = i;
    actions.Add(() => Console.WriteLine(copy));

但我无法理解为什么将副本放入 for 循环与捕获 lambda i 有何不同。

【讨论】:

因为 int 的声明在 for 循环内,所以每次都会重新创建。有 10 个不同的 int 都命名为“copy”,其中只有一个名为“i”的 int 在被柯里化的范围内。【参考方案3】:

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

注意:它实际上并不作为参考进行编译。编译器将变量提升到闭包类中,并将对“i”的访问重定向到给定闭包类中的字段“i”。不过,通常更容易将其视为参考语义。

【讨论】:

【参考方案4】:

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

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

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

【讨论】:

描述此行为的链接是 IMO 的最佳答案,点赞

以上是关于如何告诉 lambda 函数捕获副本而不是 C# 中的引用?的主要内容,如果未能解决你的问题,请参考以下文章

可变 lambda 是不是有自己的捕获值副本?

c# 在 Lambda 表达式中声明变量

第13课 lambda表达式

如何在 selenium c# 中创建自定义显式等待函数,传入 WebElements 而不是 By

本地函数与 Lambda C# 7.0

C++ lambda表达式(函数指针和function)