如何告诉 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# 中的引用?的主要内容,如果未能解决你的问题,请参考以下文章