C#中的变量作用域:未按预期工作

3
考虑以下代码
class Program
{
    static void Main(string[] args)
    {            
        string str;
        int x;

        for (x = 1; x < 10; x++)
        {
            str = "this";
        }

        Console.WriteLine(str);
        Console.ReadLine();
    }
}

当我编译时,出现以下错误:使用未赋值的本地变量“str”(我理解这部分)

如果我将for循环改为if,则可以正常工作。为什么会这样呢?(在这里感到困惑)

class Program
{
    static void Main(string[] args)
    {            
        string str;
        int x;

        if (true)
        {
            str = "this";
        }

        Console.WriteLine(str);
        Console.ReadLine();
    }
}

这种行为有什么原因?我希望在这两种情况下都能得到相同的结果。
我做错了什么?
3个回答

8

通过静态分析,编译器可以确定您的if语句将会运行,并且str将被赋值。

将您的第二个示例更改为

class Program
{
    static void Main(string[] args)
    {            
        string str;
        int x;
        bool b = true; // With "const bool" it would work, though

        if (b)
        {
            str = "this";
        }

        Console.WriteLine(str);
        Console.ReadLine();
    }
}

并且你将拥有与for循环相同的行为。
编译器无法确定您的for循环是否会执行,即使您知道它会执行,这就是为什么它告诉您有未分配的变量。更复杂的编译器可能能够看到在这种情况下您的变量是正常的,但处理所有这些情况是一个非常复杂的问题。
如果x是一个常量(在for循环中没有意义,因为您想递增它...),编译器将能够看到1确实小于10,并且不会警告您未使用的变量。当然,现在循环将永远运行,但我只是想强调编译器只能确定常量。

@Vache 静态分析是什么? - Cody
@Cody,简单的定义是“在不运行程序的情况下分析程序代码”。请参阅SO上的“What is static code analysis?”(静态代码分析是什么?)和维基百科上关于该主题的文章。 - dee-see
@Vache,基本上你的回答是我们比IDE更聪明,我认为这是不正确的。 - DidIReallyWriteThat
@CalvinSmith 请详细说明,我不明白你为什么得出那个结论。 - dee-see
@Vache 它拥有更多的内存,更快的执行时间,并且可以读取二进制文件。 - DidIReallyWriteThat
@CalvinSmith 我还是不明白你为什么这么说。编译器是由人类编写的。编写C#编译器的人决定,当没有常量涉及时,它无法确定for循环是否一定会执行。这与任何人比其他人更聪明无关。 - dee-see

6
原因是在第一个情况下,编译器考虑到循环永远不会执行的情况:
for (x = 1; x < 10; x++)
{
    str = "this";
}

因此,它假定str可能未初始化。


在第二种情况下,条件始终为true,因此编译器认为str始终已初始化:

if (true)
{
    str = "this";
}

换句话说,编译器通常会将 if(true) { x(); } 转换为 x(); - welegan
1
@PoweredByOrange:由于这是一个问答网站,您可以通过发布问题来满足您的好奇心。在此之前,我有一个问题想问您。假设我写了 int x; if (ThereIsAProofOfFermatsLastTheorem()) { x = 123; } int y = x;,您是否期望编译器尝试生成费马大定理的证明,成功并得出 x 被赋值的结论?如果答案是否定的,那么也许您想要准确地说明编译器应该推断哪些谓词为真,哪些为假,以及如何进行推断。 - Eric Lippert
我很想听听你的想法,因为如果我没记错的话,你曾经参与编写C#编译器。如果你认为这个问题值得发表或者答案太长了,我很乐意将其作为一个独立的问题发布。 - Arian Motamedi
@EricLippert 当然,它并不一定需要这样做,但是了解一些基本的数学知识(包括您将变量乘以0的示例)会导致更好的优化并减少冗余指令的数量,您认为呢?我想最终这取决于我们愿意推动编译器的程度。我不确定MSBuild进行了哪些优化。是时候深入研究Roslyn了 :) - Arian Motamedi
以上问题的答案是:如果 rand() 抛出一个被捕获的异常,而 catch 块使用了 x,那么 x 就一定不会被赋值。这些问题非常棘手,语言设计者明智地保守起见。 - Eric Lippert
显示剩余9条评论

2
编译器无法确定一个for循环是否会执行。它可能会循环0次。而if(true)语句则被编译器认为是一定会执行的。
理论上,一个足够先进的编译器可以推断出第一个代码块确实是无条件执行的,但在一般情况下解决这个问题是不可能的(你会遇到停机问题)。编译器被迫使用启发式算法来合理地猜测一个语句是否可达。如果它声明某个路径是不可达的,那么你可以确定它是不可达的。如果它表示该路径是可达的,那么它可能是可达的,也可能是误报。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接