考虑以下来源:
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
但是,如果删除注释,编译器不会发出任何警告。在我看来,它能够确定,毕竟是否设置了变量。
答案 0 :(得分:17)
我的问题是:根据C#标准,这个代码应该编译还是 这是一个编译器错误吗?
这不是错误。
C# Language Specification(4.0)的第5.3.3.29节概述了有关匿名函数的明确赋值规则,包括lambda表达式。我会在这里发布。
5.3.3.29匿名函数
对于带有正文的lambda表达式或匿名方法表达式expr(块或块) 表达)身体:
之前外部变量v的明确赋值状态 body与expr之前的v状态相同。那是明确的 外部变量的赋值状态是从上下文继承的 匿名函数。
外部变量v的明确赋值状态 expr与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); }
生成编译时错误,因为max未明确赋值 声明匿名函数的位置。例子
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#“保持简单”,并要求您通过更容易应用和可解决的规则来玩。
答案 1 :(得分:5)
您正在使用未分配的变量。即使实际分配了变量,编译器也无法从您发布的代码中推断出该变量。
无论如何都应该初始化所有局部变量,所以这很有趣,但仍然是错误的。
答案 2 :(得分:2)
当编译器执行方法的控制流分析以确定变量是否明确赋值时,它只会在当前方法的范围内查看。 Eric Lippert在this blog post中对此进行了讨论。从理论上讲,编译器可以分析从“当前方法”中调用的方法,以推断出何时明确赋值变量。
正如我之前提到的,我们可以进行过程间分析,但在实践中,实际上很快就会变得非常混乱。想象一下,一百个相互递归的方法都进入无限循环,抛出或调用组中的另一个方法。设计一个可以从复杂的调用拓扑中逻辑推断可达性的编译器是可行的,但可能需要做很多工作。此外,过程间分析仅在您拥有过程的源代码时才有效;如果其中一个方法在一个程序集中,我们必须使用的是元数据呢?
请记住,您的代码示例并非真正的单一方法。匿名方法将被重构为另一个类,它的一个实例将被创建,它将调用一个类似于你的定义的方法。此外,编译器需要分析delegate
类的定义以及Action
的定义,以推断您提供的方法是否实际执行。
因此,尽管编译器知道变量在该上下文中是可达的理论可能性范围内,但由于编写编译器的复杂性,编译器编写者故意选择不这两者,并且(可能)重要的)增加编制程序所需的时间。
答案 3 :(得分:1)
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方法,而不是实际的方法。