lambda表达式如何共享局部变量?

时间:2013-10-28 12:15:23

标签: c#

我正在阅读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。

4 个答案:

答案 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