Lambda分配局部变量

请考虑以下来源:

static void Main(string[] args) { bool test; Action lambda = () => { test = true; }; lambda(); if (test) Console.WriteLine("Ok."); } 

它应该编译,对吗? 嗯,事实并非如此。 我的问题是:根据C#标准,这个代码应该编译还是这个编译器错误?


错误消息:

 Use of unassigned local variable 'test' 

注意:知道 ,如何修复错误,我部分知道 ,为什么会发生。 但是,局部变量是无条件分配的,我想,编译器应该注意到,但事实并非如此。 我想知道为什么。


评论答案:C# 允许声明未分配的变量,这实际上非常有用,即。

 bool cond1, cond2; if (someConditions) { cond1 = someOtherConditions1; cond2 = someOtherConditions2; } else { cond1 = someOtherConditions3; cond2 = someOtherConditions4; } 

编译器正确地编译了这段代码,我认为,保留未分配的变量实际上会使代码更好一些,因为:

  • 它告诉读者,稍后会分配值(主要可能在以下条件语句中)
  • 强制程序员在内部条件的所有分支中分配变量(如果从一开始就是这个代码的目的),因为如果其中一个分支没有分配其中一个,编译器将拒绝编译代码。

在边缘 :这更有趣。 考虑C ++中的相同示例:

 int main(int argc, char * argv[]) { bool test; /* Comment or un-comment this block auto lambda = [&]() { test = true; }; lambda(); */ if (test) printf("Ok."); return 0; } 

如果您对块进行注释,则编译以警告结束:

 main.cpp(12): warning C4700: uninitialized local variable 'test' used 

但是,如果删除注释,编译器不会发出任何警告。 在我看来,它能够确定,毕竟是否设置了变量。

我的问题是:根据C#标准,这个代码应该编译还是这个编译器错误?

这不是一个错误。

C#语言规范 (4.0)的第5.3.3.29节概述了有关匿名函数的明确赋值规则,包括lambda表达式。 我会在这里发布。

5.3.3.29匿名函数

对于具有正文(块或表达式)主体的lambda表达式或匿名方法表达式expr:

  • 在body之前的外部变量v的明确赋值状态与在expr之前的v的状态相同。 也就是说,外部变量的明确赋值状态是从匿名函数的上下文inheritance的。

  • expr之后的外部变量v的明确赋值状态与expr之前的v的状态相同。

这个例子

 delegate bool Filter(int i); void F() { int max; // Error, max is not definitely assigned Filter f = (int n) => n < max; max = 5; DoWork(f); } 

生成编译时错误,因为在声明匿名函数的地方没有明确赋值。 这个例子

 delegate void D(); void F() { int n; D d = () => { n = 1; }; d(); // Error, n is not definitely assigned Console.WriteLine(n); } 

因为在匿名函数中赋值给n对匿名函数外的n的明确赋值状态没有影响,所以也会产生编译时错误。

您可以看到这如何适用于您的具体示例。 在声明lambda表达式之前未特别指定变量test 。 在执行lambda表达式之前未特别指定它。 并且在lambda表达式执行完成后没有专门分配它。 根据规则,编译器不会认为变量在if语句中被读取时被明确赋值。

至于为什么,我只能重复我在这个问题上所阅读的内容,而且只有我能记住的内容,因为我无法生成链接,但是C#不会尝试这样做,因为尽管这是一个微不足道的案例,眼睛可以看到更常见的情况是,这种类型的分析是非常重要的,实际上可能等于解决暂停问题。 因此,C#“保持简单”,并要求您通过更容易应用和可解决的规则来玩。

您正在使用未分配的变量。 即使实际分配了变量,编译器也无法从您发布的代码中推断出该变量。

无论如何都应该初始化所有局部变量,所以这很有趣,但仍然是错误的。

当编译器执行方法的控制流分析以确定变量是否被明确赋值时,它将仅在当前方法的范围内查看。 Eric Lippert在这篇博文中对此进行了讨论。 从理论上讲,编译器可以分析从“当前方法”中调用的方法,以推断出何时明确赋值变量。

正如我之前提到的,我们可以进行过程间分析,但在实践中,实际上很快就会变得非常混乱。 想象一下,一百个相互递归的方法都会进入无限循环,抛出或调用组中的另一个方法。 设计一个可以从复杂的调用拓扑中逻辑推断可达性的编译器是可行的,但可能需要做很多工作。 此外,过程间分析仅在您拥有过程的源代码时才有效; 如果其中一个方法在一个程序集中,我们必须使用的是元数据呢?

请记住,您的代码示例并非真正的单一方法。 匿名方法将被重构为另一个类,将创建它的一个实例,并且它将调用类似于您的定义的方法。 此外,编译器需要分析delegate类的定义以及Action的定义,以推断您提供的方法是否实际执行。

因此,尽管编译器知道变量在该上下文中是可达的理论可能性范围内,但编译器编写者故意选择不同时由于编写编译器的复杂性,以及(可能显着的)增加及时编译程序。

ECMA标准第8.3节变量和参数的摘录:

在获得其值之前,应分配变量。 这个例子

 class Test { static void Main() { int a; int b = 1; int c = a + b; // error, a not yet assigned } } 

导致编译时错误,因为它在分配值之前尝试使用变量a。 管理明确分配的规则在第12.3节中定义。

因此,它声明必须在使用变量之前分配变量,否则会导致编译器错误。 因为您正在创建委托并调用它,所以委托调用中包含的方法在技术上是未知的。 因此编译器不会想出来。 记住它是被调用的Delegate的Invoke方法而不是实际的方法。

ECMA EC#标准