LINQ 匿名方法中的局部变量作用域(闭包)

本文关键字:作用域 闭包 局部变量 方法 LINQ | 更新日期: 2023-09-27 18:33:34

Linq Query 中声明的局部变量的作用域是什么?

我正在编写以下代码

   static void Evaluate()
    {
        var listNumbers = Enumerable.Range(1, 10).Select(i => i);
        int i = 10;
    }

编译器在 int i=10 行上标记错误,指出

A local variable named 'i' cannot be declared in this scope because it would give a different meaning to 'i', which is already used in a 'child' scope to denote something else 

我不明白为什么会出现此错误。

我的理解是,i将在第一行(在foreach循环中(之后超出范围。所以i可以再次声明。

实际行为是在第一行(在 foreach 循环中(之后无法访问i,这是正确的。但i不能再宣布了。这似乎很奇怪。

编辑这是根据安德拉斯的回答提出的以下问题。答案非常好,但引起了进一步的怀疑。

  static void Evaluate3()
    {
        var listNumbers = Enumerable.Range(1, 10).Select(i => i);
        var listNumbers1 = Enumerable.Range(1, 10).Select(i => i);
    }

基于函数的逻辑 评估 .选择(i=>i(和int i=10,都是i,都是功能块的局部,因此是复杂误差。

函数 Evaluate3 应该编译得不如方法块中有两个 i,但它编译成功,没有任何警告/错误。

问题,要么评估

和评估 3 都不应该编译,要么两者都应该编译。

LINQ 匿名方法中的局部变量作用域(闭包)

这里要注意的关键事实是声明:

int i;

。从头到尾在整个封闭范围内生效 - 而不仅仅是从声明点开始。 在 .Net 中,局部变量的声明只是编译器为整个范围保留该名称和局部的指令。 这意味着一旦声明,它就已经为之前和之后的所有行保留。

实际上,这意味着您实际上应该将Evaluate解读为:

static void Evaluate()  
{  
  int i;  
  var listNumbers = Enumerable.Range(1, 10).Select(i => i);  
  i = 10;
} 

如果你确实相应地编写了你的方法,你会看到编译器错误发生在lambda声明上 - 这是完全合理的。 值得庆幸的是,从人类的角度来看,C# 编译器足够聪明,可以认识到代码的排序对我们很重要,它实际上将编译器错误分配给作为第二个或后续声明的源代码行;因此,为什么在您的Evaluate版本中,它发生在int i = 10;行上。 有了对函数本地i的实际生存期的了解,编译器是正确的:在那里使用i将与之前在lambda中使用i冲突。

您可以使用显式作用域来避免这种情况:

static void Evaluate()  
{
  var listNumbers = Enumerable.Range(1, 10).Select(i => i);
  {
    int i = 10;
  }
}

Evaluate3的情况下,您只需注意到,虽然两个 lambda 共享父函数作用域,但它们也有自己的作用域,并且它们的i就是在那里声明的 - 这就是为什么它们不会相互干扰(它们实际上是同级作用域(。

顺便说一下,EvaluateEvaluate3最终可以简化为:

static void Evaluate()
{
  { 
    int i;
  }
  int i; //<-- error
}
static void Evaluate3()
{
   { 
     int i;
   }
   { 
     int i;
   }
   //fine here - both i's are in different scopes.
}

这实际上是我之前使用显式作用域的第二种情况 - 也就是说,在同一函数的不同作用域中,i实际上在每个作用域中都有不同的类型。 就像我说的 - 我再也没有这样做过,有问题的代码不再:)

从规范:

The scope of a local variable declared in a local-variable-declaration is the block in which the declaration occurs. It is an error to refer to a local variable in a textual position that precedes the local-variable-declarator of the local variable.

您的 lambda i => i在局部变量的范围内,即使它是事先声明的,int i = 10也是如此。编译器不会因为您在声明之前使用 i 而引发错误,而是会帮助指出您已经使用 i 来引用其他内容,并且此声明将更改它。

编辑:更新后:

在第一种情况下,您的第一个i包含在 lambda 中,但您的第二个i包含包括 lambda 在内的整个Evaluate方法——因此您会收到错误。

在第二种情况下,您的第一个i被包含在它的 lambda 中,而你的第二个i被封闭在其 lambda 中 - i都不在另一个的范围内,因此没有错误。

您的段落"基于...我都是功能块的本地..."不正确 - 第一个i不是功能块的本地,而是 lambda 的本地。