我正在阅读lambda表达式,我已经看过这个例子,
示例1:
static Func<int> Natural()
{
int seed = 0;
return () => seed++; // Returns a closure
}
static void Main()
{
Func<int> natural = Natural();
Console.WriteLine (natural()); // output : 0
Console.WriteLine (natural()); // output : 1
}
示例2:
static Func<int> Natural()
{
return() => { int seed = 0; return seed++; };
}
static void Main()
{
Func<int> natural = Natural();
Console.WriteLine (natural()); // output : 0
Console.WriteLine (natural()); // output : 0
}
我无法理解为什么第一个示例输出为0和1。
答案 0 :(得分:6)
因为第二个例子(int seed = 0
)中的初始化代码是在每次调用时运行的。
在第一个示例中,seed
是一个捕获的变量,它存在于方法之外,因为只有一个实例在调用之间保留其值。
更新:回应David Amo的评论,解释。
选项1)
static Func<int> Natural()
{
int seed = 0;
return () => seed++; // Returns a closure
}
选项2)
static Func<int> Natural()
{
return() => { int seed = 0; return seed++; };
}
选项3)
static Func<int> Natural()
{
int seed = 0;
return () => { seed = 0; return seed++;}; // Returns a closure
}
选项3返回与选项2相同的值,但在内部作为选项1使用。seed
是在Natural
内定义的变量,但由于它由委托捕获,因此它继续存在于方法已经退出。
您可以用来查看正在发生的事情的另一项测试是
static Func<int> Natural()
{
int seed = 1;
Func<int> returnValue = () => { return seed++; };
seed = 2;
return returnValue;
}
答案 1 :(得分:2)
lambda表达式可以引用定义它的方法的局部变量和参数(外部变量)
lambda表达式引用的外部变量称为捕获变量。捕获变量的lambda表达式称为闭包。
在实际调用委托时评估捕获的变量,而不是在捕获变量时评估:
int factor = 2;
Func<int, int> multiplier = n => n * factor;
factor = 10;
Console.WriteLine (multiplier (3)); // 30
Lambda表达式本身可以更新捕获的变量:
int seed = 0;
Func<int> natural = () => seed++;
Console.WriteLine (natural()); // 0
Console.WriteLine (natural()); // 1
Console.WriteLine (seed); // 2
捕获的变量的生命周期延长到委托的生命周期。在下面的示例中,当Natural完成执行时,局部变量种子通常会从范围中消失。但是因为已经捕获了种子,它的生命周期延长到捕获代表的生命周期,自然:
static Func<int> Natural()
{
int seed = 0;
return () => seed++; // Returns a closure
}
static void Main()
{
Func<int> natural = Natural();
Console.WriteLine (natural()); // 0
Console.WriteLine (natural()); // 1
}
在lambda表达式中实例化的局部变量在每次调用委托实例时都是唯一的。如果我们重构前面的例子来实例化lambda表达式中的种子,我们得到一个不同的(在这种情况下,不合需要的)结果:
static Func<int> Natural()
{
return() => { int seed = 0; return seed++; };
}
static void Main()
{
Func<int> natural = Natural();
Console.WriteLine (natural()); // 0
Console.WriteLine (natural()); // 0
}
答案 2 :(得分:0)
int seed=0
在匿名函数的范围内,因此每次调用lambda表达式时都会调用它。它返回0然后增加1,但再次调用该函数时设置为0。
在第一个示例中,在该范围之外声明了种子变量,并且只有一个实例在调用之间保留其值。
答案 3 :(得分:0)
查看编译器生成的代码类型可能有助于您了解闭包的工作方式。
在第一个示例中,lambda表达式被编译为闭包,封装了seed
变量。这意味着编译器将生成一个包含seed
实例的类,并且对该lambda的所有调用都将增加该实例。
static Func<int> Natural()
{
int seed = 0;
return () => seed++; // Returns a closure
}
对于上面的lambda,编译器将生成类似这样的内容,并返回此类的实例:
[CompilerGenerated]
private sealed class <>c__DisplayClass1
{
public int seed;
public int <Natural>b__0()
{
return seed++;
}
}
所以,代码如下:
Func<int> natural = Natural();
Console.WriteLine (natural()); // output : 0
Console.WriteLine (natural()); // output : 1
实际上与
相同<>c__DisplayClass1 closure = //...
Console.WriteLine ( closure.<Natural>b__0() ); // outputs 0
Console.WriteLine ( closure.<Natural>b__0() ); // outputs 1