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

本文关键字:共享 局部变量 表达式 何共享 lambda | 更新日期: 2023-09-27 18:37:18

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

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

因为第二个示例(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。 seedNatural 内部定义的变量,但由于它被委托捕获,因此在方法退出后它继续存在。

您可以使用的另一个测试来查看正在发生的事情是

static Func<int> Natural()
{
  int seed = 1;
  Func<int> returnValue = () => { return seed++; };
  seed = 2;
  return returnValue;
}

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
}

int seed=0在匿名函数的作用域内,因此每次调用 lambda 表达式时都会调用。它返回 0,然后递增 1,但在再次调用函数时设置为 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