【问题标题】:Initialization of instance fields vs. local variables实例字段与局部变量的初始化
【发布时间】:2010-12-05 07:06:50
【问题描述】:

我一直想知道为什么在下面的例子中可以初始化实例字段(依赖于它将有它的默认值)并访问它,而局部变量显然是必须初始化,即使我将它初始化为默认值,它还是会得到......

  public class TestClass
  {
    private bool a;

    public void Do()
    {
      bool b; // That would solve the problem: = false;
      Console.WriteLine(a);
      Console.WriteLine(b); //Use of unassigned local variable 'b'
    }
  }

【问题讨论】:

    标签: c# .net clr


    【解决方案1】:

    对于局部变量,编译器对流程有很好的了解——它可以看到变量的“读取”和变量的“写入”,并证明(在大多数情况下)第一次写入将发生在之前第一次阅读。

    实例变量并非如此。考虑一个简单的属性——你怎么知道有人在得到它之前是否会设置它?这使得强制执行明智的规则基本上是不可行的 - 所以您必须确保在构造函数中设置了 all 字段,或者允许它们具有默认值。 C# 团队选择了后一种策略。

    【讨论】:

    • 事情是,我喜欢实例字段的行为......因此,可能决定将局部变量“隐式”初始化为其默认值,但编译器团队决定反对它基于假设它会减少由于使用“未初始化”变量而导致的错误。
    • 同样将变量初始化为默认值有运行时成本,很难确定一个对象的所有字段是否在虚拟方法存在的情况下都被明确分配,因此将所有字段初始化为默认值的运行时成本是支付,如果是本地人,则不需要成本,因为可以确定是否在读取之前分配了局部变量,如您所说。
    • @Frank:是的,编译器团队认为尝试阻止人们创建错误是个好主意。这对我来说似乎是一个不错的决定 :) 如果您想为所有局部变量分配默认值,这样做很容易,但为什么要麻烦呢?确定分配的规则很少让您失望,并且很容易在仅在这些情况中给出默认值。安全是一件好事:)
    • 我认为几乎所有答案都对该问题做出了有效贡献,但为了将此问题标记为已回答,我选择了这个问题作为第一个。
    【解决方案2】:

    实例变量有一个默认值。来自 C# 3.0 规范:

    5.1.2.1 类中的实例变量

    一个类的实例变量来了 当一个新的实例存在时 该类已创建,并停止 没有引用时存在 该实例和该实例的 终结器(如果有)已执行。

    实例的初始值 类的变量是默认值 变量类型的值(§5.2)。

    为了明确分配 检查,一个实例变量是 被认为是最初分配的。

    【讨论】:

    • 我认为问题的重点是问为什么实例变量有默认值,而局部变量没有。
    • 这是编译器能证明什么的问题。编译器可以证明一个局部变量没有被初始化,但是一个类或实例变量有很多访问路径并且不能以某种方式证明。
    【解决方案3】:

    这是一个编译器限制。编译器试图阻止您尽可能使用未分配的变量,这是一件好事,因为使用未初始化的变量曾经是旧 C 代码中错误的常见来源。

    然而,编译器无法知道实例变量是否在您点击该方法调用之前被初始化,因为它可以由任何其他方法设置,外部代码可以按任何顺序调用该方法。

    【讨论】:

    • 这不是编译器限制,这是由运行时强制执行的,运行时要求初始化所有局部变量并确保将新对象的所有字段设置为默认值。
    • 我的意思是它没有给出编译错误是编译器的限制,这从我的回答中很明显。
    • Pop:我找不到 CLI 规范中指出 CLI 需要初始化所有本地变量的任何部分。如果 CLI 中确实有这样的规则,您能否向我们指出它在哪里?
    • @Eric Lippert,ECMA 335 - 12.4.1 方法调用
    • @PopCatalin 在 ECMA 中 CLI 要求字段清零的位置是什么?
    【解决方案4】:

    隐式构造函数为您初始化实例变量。即使您指定了一个 c'tor 但没有初始化一个字段,它也会作为在堆上创建对象的一部分为您完成。堆栈局部变量并非如此。

    【讨论】:

    • 不正确,隐式构造函数仅在没有专门创建构造函数时存在,您会混淆术语,运行时将字段初始化为默认值而不是隐式构造函数(类可能没有参数 less构造函数)
    • 我的第一句话是专门针对OP发布的代码。在提供非默认 c'tor 的情况下,我的第二句话更为笼统。我将我的答案和您的评论解析为相同。我错过了什么?
    • 就像我说的那样,您混淆了术语,在他的情况下,隐式无参数构造函数“什么也不做”,它只是作为合同来表明可以实例化类。将字段初始化为默认值而不是构造函数的是运行时,在存在另一个带参数的构造函数时,也可能缺少参数隐式构造函数。
    • @psychotik,更清楚地说,“隐式构造函数”是 C# 编译器在代码中没有定义其他构造函数时创建的无参数构造函数。它并不总是被创建,只有在那种特殊情况下,并且类根本不需要有一个无参数的构造函数。
    • Pop Catalin 是正确的。也就是说,您的第一句话——隐式默认构造函数初始化变量——是不正确的。你的第二句话——当运行时在堆上创建一个对象时为你完成——是正确的。
    【解决方案5】:

    它受 C# 中的明确赋值规则约束。变量在被访问之前必须明确赋值。

    5.3 明确赋值

    在函数成员的可执行代码中的给定位置,如果编译器可以通过特定的静态流分析(第 5.3.3 节)证明该变量已自动初始化,则称该变量已被明确赋值或者是至少一项任务的目标。

    5.3.1 初始赋值变量

    以下类别的变量被归类为初始分配:

    • 静态变量。

    • 类实例的实例变量。

    • 最初分配的结构变量的实例变量。

    • 数组元素。

    • 值参数。

    • 参考参数。

    • 在 catch 子句或 foreach 语句中声明的变量。

    5.3.2 最初未赋值的变量

    以下类别的变量被归类为最初未赋值:

    • 最初未分配的结构变量的实例变量。

    • 输出参数,包括struct实例构造函数的this变量。

    • 局部变量,在 catch 子句或 foreach 语句中声明的变量除外。

    【讨论】:

      【解决方案6】:

      当为新对象实例分配一块内存时,运行时会在整个块中写入零,确保新对象以已知状态开始 - 这就是为什么整数默认为 0,双精度数默认为 0.0,指针& 对 null 的对象引用,等等。

      理论上,可以对作为方法调用的一部分分配的堆栈帧执行相同的操作。虽然开销会很高 - 它会极大地减慢对其他方法的调用速度,因此不会尝试。

      【讨论】:

      • 对不起,我不太明白归零的事情,但这听起来很有趣......
      【解决方案7】:

      这实际上只是警告能够告诉你什么的问题。警告确实无法确保其他方法没有初始化类变量,因此它只会警告可以确定未初始化的方法。

      此外,这是一个警告而不是错误,因为使用未分配的变量在技术上没有任何问题(保证为“假”),但未分配它可能是一个逻辑错误。

      【讨论】:

      • 警告是什么意思?在 Visual Studio 中,默认情况下它会显示为错误。此外,我有点意识到,如果变量已初始化,那么在本地计算会容易得多 - 我很想知道为什么我在本地场景中也没有获得默认值。
      • @Frank,因为初始化为默认值的变量和未初始化的变量之间存在差异,有时您可能需要第一个,但如果您碰巧读取了未初始化的变量,则不应获得默认值在所有情况下,因为这可能只是一个编程错误而不是预期的结果。
      • 你是对的,这是 .NET 中的一个错误(我对其他编译器感到困惑。)来自文档:“C# 编译器不允许使用未初始化的变量......” C# 编译器允许初始化非局部变量的原因是因为它无法判断它们是否已被初始化。只有局部变量可以被编译器证明不被初始化。
      • Adam:虽然我同意你的观点,但你实际上是在说反话。编译器从不试图证明一个局部变量还没有被初始化。相反,它未能证明一个局部变量被初始化。证明否定给我们没有有用的信息!正是无法证明积极的一面给了我们可以采取行动的东西。
      • 一个例子:“int x, y = 0; if (0 == y*0) x = 10; print(x);”我们在 C# 3 中给出了一个明确的赋值错误。我们没有证明 x 总是未赋值的——如果我们这样做了,那么我们的证明系统将是不正确的,因为显然 x 总是被赋值。相反,因为编译器不够复杂,无法证明 x 总是被赋值,所以我们给出了一个错误。我们未能证明肯定是错误,而不是我们成功证明否定
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-17
      • 2016-09-07
      • 2012-10-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多