3

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

4 回答 4

6

因为第二个示例 ( int seed = 0) 中的初始化代码在每次调用时运行。

在第一个示例中,seed是一个存在于方法之外的捕获变量,因为只有一个实例,它的值在调用之间保留。

更新:回应大卫阿莫的评论,解释。

选项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;
}
于 2013-10-28T12:19:18.970 回答
4

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
}
于 2013-10-28T12:48:09.533 回答
0

int seed=0位于匿名函数的范围内,因此每次调用 lambda 表达式时都会调用。它返回 0,然后加一,但在再次调用该函数时设置为 0。

在第一个示例中,在该范围之外声明了种子变量,并且由于只有一个实例,因此它的值在调用之间保留。

于 2013-10-28T12:20:14.020 回答
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
于 2013-10-28T12:53:23.743 回答