【发布时间】:2015-05-23 13:58:31
【问题描述】:
我的印象是 C# 中的 lambda 表达式上下文包含对其中使用的父函数范围的变量的引用。考虑:
public class Test
{
private static System.Action<int> del;
public static void test(){
int i = 100500;
del = a => System.Console.WriteLine("param = {0}, i = {1}", a, i);
del(1);
i = 10;
del(1);
}
public static void Main()
{
test();
}
}
输出
param = 1, i = 100500
param = 1, i = 10
但是,如果这是真的,那么以下内容将是非法的,因为 lambda 上下文将引用超出范围的局部变量:
public class Test
{
private static System.Action<int> del;
public static void test(){
int i = 100500;
del = a => System.Console.WriteLine("param = {0}, i = {1}", a, i);
}
public static void Main()
{
test();
del(1);
}
}
但是,这会编译、运行和输出
param = 1, i = 100500
这意味着要么发生了一些奇怪的事情,要么上下文保留了局部变量的值,而不是对它们的引用。但如果这是真的,它必须在每次 lambda 调用时更新它们,而且我看不出当原始变量超出范围时它会如何工作。此外,在处理大值类型时,这似乎会产生开销。
我知道,例如,在 C++ 中,这是 UB(已在回复 this question 时确认)。
问题是,这是 C# 中定义明确的行为吗? (我认为 C#确实有一些 UB,或者至少有一些 IB,对吧?)
如果定义明确,它是如何以及为什么实际起作用的? (实现逻辑会很有趣)
【问题讨论】:
-
Lambda Expressions: "Lambda 可以引用外部变量 (...),这些变量在定义 lambda 函数的方法的范围内,或在包含 lambda 表达式的类型的范围内。以这种方式捕获的变量被存储以供 lambda 表达式使用,即使这些变量会超出范围并被垃圾回收......”(我的重点)
-
这应该回答你的问题:stackoverflow.com/questions/9591476/…
-
Skeet 的article about closures 更深入一点。此外,此答案包含有关实际生成代码的一些信息:stackoverflow.com/a/7142956/996081
-
发生的情况是编译器生成了一个类,而您正在使用的变量被“提升”到这个类上。在您的方法内部,构造了该类的一个新实例,并且该方法中该变量的所有用法都经过该实例。您声明的实际委托也作为普通方法提升到此类。
-
因此它不捕获值,而是捕获变量。