【问题标题】:Why aren't unassigned local variables automatically initialized?为什么未分配的局部变量不会自动初始化?
【发布时间】:2011-12-09 11:47:50
【问题描述】:

似乎没有办法在您的代码中包含未分配的局部变量或检查它们,因为编译器会吐出Use of unassigned local variable 错误。

为什么编译器在编译时不在这些变量上使用default(T)

即使值类型更难做,在这种情况下引用类型也可以很容易地初始化为null,对吧?

这是一些测试代码:

public void Test ( )
{
    int x;
    string s;

    if ( x == 5 )
        Console.WriteLine ( 5 );

    if ( s != null )
        Console.WriteLine ( "s" );
}

返回:

Use of unassigned local variable 'x'
Use of unassigned local variable 's'

更新:

对于那些声称这是出于正当理由不允许这样做的人,为什么在班级级别上允许这样做?

public class C
{
    public int X;
    public string S;

    public void Print ( )
    {
        Console.WriteLine ( X );
        Console.WriteLine ( S );
    }
}

这段代码编译得很好。

为什么在类级别上可以,但在方法级别上不行?

【问题讨论】:

  • 编译器想要编译器想要的。
  • 我认为这是一个设计决策,仅此而已,就像 C/C++ 一样。
  • 当您忘记初始化引用类型的变量时,您宁愿遇到编译时错误还是(运行时)空指针异常?
  • 也许编译器鼓励你使用良好的编码实践。
  • CLR 实际上确实将该变量初始化为默认值(T)。在 vb.net 或 C++/CLI 中可以依赖默认值。但不是在 C# 中,语言设计者选择将其设为“很可能是错误”的错误。就是这样,很多 C 和 C++ 程序员都被它咬得很厉害。

标签: c# .net variables compiler-construction initialization


【解决方案1】:

我看到你更新了你的问题,所以我会更新我的答案。您的问题有两个部分,一个与local variables 有关,另一个与instance variables on a class instance 有关。然而,首先,这并不是真正的编译器设计决策,而是语言设计决策。

Spec 12.3.1/12.3.2 节

局部变量

我们知道为什么你可以定义一个变量而不给它一个值。一个原因,一个这样的例子:

int x;
// do stuff
x = 5;  // Wow, I can initialize it later!
Console.WriteLine(x);

标准定义了为什么这是有效的代码。现在,我不在 C# 设计团队中,但他们不会自动为您初始化代码是有道理的(除了当您实际上想要它时性能受到影响自动初始化)。

假设上面的代码是你的意图,但你忘记初始化x = 5;。如果编译器已经自动为你初始化了变量,代码会编译,但它不会像你期望的那样做。

当然,这是一个微不足道的例子,但这是语言设计者的一个非常好的设计决定,因为它可以避免许多试图弄清楚为什么某些东西没有按预期工作的麻烦。

作为旁注,我想不出你为什么要定义代码而不给它分配一些东西,或者使用默认值(在每种情况下),对我来说这可能是一个错误,我敢肯定,编译器设计者可能已经确定了这一点。

类实例变量

类级别成员由最初分配的标准定义。事实上,公平地说,在 catchforeachusing 语句中声明的变量之外的局部变量最初是未赋值的。所以说真的,这是一个标准问题,而不是编译器问题。

如果我要尝试猜测为什么类实例的实例变量会出现这种情况,我会说这与在堆上分配内存的方式有关,因为这是分配类的地方。当一个类在堆上分配时,它的所有成员都必须与它一起在堆上进行初始化和分配。不仅可以在类成员中执行此操作而不是在局部变量中执行此操作,必须以这种方式完成。他们只是不能不被分配。

【讨论】:

  • 谢谢,我添加了更多细节来解决这个问题。为什么在课堂上可以这样做?如果我忘记初始化相同的变量,我应该还会看到意外行为吗?
  • @JoanVenge - 我已对您的编辑添加了回复。当然,这仍然是一个猜测,但我认为它可能是有效的。
【解决方案2】:

C# 是一种“成功之坑”语言。

这是一个设计决定,因为该语言完全能够允许您使用尚未明确分配的本地语言。然而,这些变量的使用通常是错误的,代码路径由于某种原因没有设置值。为避免此类错误,编译器要求在使用之前分配所有局部变量。

【讨论】:

  • 保证非堆栈变量初始化为给定类型所代表的二进制零(通常为 0、null、...)。
【解决方案3】:

1 为什么编译器不允许使用未初始化的变量?

因为防止这种情况可以促进良好的编程。

2 为什么编译器允许使用未初始化的类成员?

因为不可能以任何准确度对此进行跟踪。

【讨论】:

    【解决方案4】:

    如果您建议将引用类型初始化为 null,而不是当前行为(错误代码会导致编译时错误),那么当您取消引用未初始化的变量时,您会收到运行时错误。这真的是你想要的吗?

    【讨论】:

    • 如果您没有初始化变量并且知道编译器会为您将其初始化为 null,那么您不会有运行时 null ref 异常,而不是将其初始化为 null 然后忘记检查了。
    • @JoanVenge 有意识地做出决定和让别人替你做决定是有区别的。
    • @JoanVenge 你似乎认为你永远不会犯错。由于我是人类并且确实会犯错误,因此我想在编译时而不是运行时找出它们。
    • @CraigStuntz:我在问题中添加了更多细节。我不认为自己不会犯错,但为什么在班级级别上允许这样做?
    • @JoanVenge “更多细节”完全改变了问题。推理在班级层面上是不同的。在类级别,必须在构造函数运行时分配所有字段,否则您不能引用构造函数中的字段。与不允许在构造函数中使用虚拟成员的 C++ 或 Scala 等语言形成对比,后者提供了以可预测的方式分配成员的方法。
    【解决方案5】:

    考虑以下代码:

    void blah(IDictionary<int,int> dict)
    {
      for (int i=0; i<10; i++)
      {
        if ((i & 11) != 0)
        {
            int j;
            dict.TryGetValue(i, out j);
            System.Diagnostics.Debug.Print("{0}",j);
            j++;
        }
      }
    }
    

    假设IDictionary&lt;int,int&gt; 的传入实现的TryGetValue 方法从未真正写入j [如果它是用C# 编写的,则不可能,但如果它是用另一种语言编写的,则可能]。应该期望代码打印什么?

    C# 标准中没有要求在代码离开if 语句时维护j,但也没有要求在循环迭代之间将其重置为零。在某些情况下,强制采取任何一种行动都会产生额外的成本。标准没有这样做,而是简单地允许当调用TryGetValue 时,j 可以任意保持零或它在范围内时保持的最后一个值。这种方法避免了不必要的成本,但如果允许代码在重新进入范围和写入时间之间看到j 的值(事实上将未初始化的变量作为out用另一种语言编写的代码的参数会暴露其值,这可能是无意的)。

    【讨论】:

      【解决方案6】:

      因为你想要什么?您希望 x 默认为零,而我希望它为 5...

      如果他们将 0 分配给 int(s) 并且全世界都开始假设,那么他们将在某个时候更改为 -1,这将破坏全球许多应用程序。

      我认为在 VB6 中变量被默认分配给某些东西,它并没有看起来那么好。

      当您使用 C# 或 C++ 时,您使用所需的值而不是为您分配的编译器。

      【讨论】:

      • 嗯...除了保证非堆栈变量被初始化为零或空。语言以这种方式定义它,因此将来不会改变。
      • 我明白你的意思,但你为什么不抱怨这种行为存在于班级层面呢?此外,如果他们使用 0 作为默认值,那么以后他们无法将其更改为 -1。同样的原因,他们不会因为重大问题而改变他们之前所做的很多事情。
      猜你喜欢
      • 1970-01-01
      • 2020-10-16
      • 2011-12-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-03
      • 1970-01-01
      相关资源
      最近更新 更多