11

考虑以下来源:

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

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

4

4 回答 4

17
于 2013-01-08T21:47:47.323 回答
5

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

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

于 2013-01-08T21:22:59.420 回答
2

当编译器对方法执行控制流分析以确定是否明确分配了变量时,它只会查看当前方法的范围。Eric Lippert 在这篇博文中讨论了这个问题。理论上,编译器可以分析从“当前方法”中调用的方法,以推断何时明确分配了变量。

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

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

因此,虽然编译器知道该变量在该上下文中是可访问的在理论上的可能性范围内,但编译器编写者故意选择不这样做,因为为它编写编译器的复杂性以及(可能显着的)增加及时编译程序。

于 2013-01-08T22:02:51.890 回答
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 节中定义。

因此,它声明必须在使用变量之前对其进行赋值,否则会导致编译器错误。因为您正在创建一个委托并调用它,所以委托调用中包含的方法在技术上是未知的。因此编译器不会弄明白。请记住,调用的是委托的 Invoke 方法,而不是实际的方法。

C# 的 ECMA 标准

于 2013-01-08T21:25:31.503 回答