gpt4 book ai didi

c# - 为什么局部变量需要初始化,而字段不需要?

转载 作者:行者123 更新时间:2023-12-01 18:11:39 26 4
gpt4 key购买 nike

如果我在我的类(class)中创建一个 bool,就像 bool check ,它默认为 false。

当我在我的方法中创建相同的 bool 时,bool check (而不是在类中),我收到错误“使用未分配的局部变量检查”。为什么?

最佳答案

Yuval 和 David 的回答基本正确;总结:

  • 使用未分配的局部变量是一个可能的错误,编译器可以以低成本检测到这一点。
  • 使用未分配的字段或数组元素不太可能是错误,并且更难在编译器中检测条件。因此,编译器不会尝试检测字段的未初始化变量的使用,而是依赖于对默认值的初始化以使程序行为具有确定性。

  • 大卫回答的评论者问为什么不可能通过静态分析检测未分配字段的使用;这是我想在这个答案中扩展的一点。

    首先,对于任何变量,无论是局部变量还是其他变量,实际上都无法准确确定变量是已赋值还是未赋值。考虑:
    bool x;
    if (M()) x = true;
    Console.WriteLine(x);

    问题“x 是否已分配?”相当于“M() 是否返回 true?”现在,假设如果费马大定理对所有小于 110 gajillion 的整数都为真,则假设 M() 返回真,否则为假。为了确定 x 是否明确赋值,编译器本质上必须生成费马大定理的证明。编译器没有那么聪明。

    因此,编译器为 locals 做的是实现一个快速的算法,并在没有明确分配 local 时高估。也就是说,它有一些误报,它说“我不能证明这个本地是分配的”,即使你我都知道它是。例如:
    bool x;
    if (N() * 0 == 0) x = true;
    Console.WriteLine(x);

    假设 N() 返回一个整数。你和我都知道 N() * 0 将是 0,但编译器不知道。 (注意:C# 2.0 编译器确实知道这一点,但我删除了该优化,因为规范没有说编译器知道这一点。)

    好的,那么到目前为止我们知道什么?本地人要得到一个确切的答案是不切实际的,但我们可以廉价地高估未分配的性,并得到一个相当不错的结果,该结果在“让你修复你不清楚的程序”方面出错。那很好。为什么不对字段做同样的事情呢?也就是说,做一个明确的分配检查器,便宜地高估?

    那么,本地初始化有多少种方式呢?它可以在方法的文本中指定。它可以在方法文本中的 lambda 内赋值;那个 lambda 可能永远不会被调用,所以这些赋值是不相关的。或者它可以作为“out”传递给另一个方法,此时我们可以假设它在方法正常返回时被赋值。这些是本地被分配的非常明确的点,它们就在与声明本地的方法相同的地方。确定本地人的明确分配只需要本地分析。方法往往很短——一个方法中的代码远少于一百万行——因此分析整个方法非常快。

    现在字段呢?字段当然可以在构造函数中初始化。或者一个字段初始值设定项。或者构造函数可以调用初始化字段的实例方法。或者构造函数可以调用初始化字段的虚拟方法。或者构造函数可以调用另一个类中的方法,该类可能位于库中,用于初始化字段。静态字段可以在静态构造函数中初始化。静态字段可以由其他静态构造函数初始化。

    本质上,字段的初始值设定项可以在整个程序中的任何位置,包括将在尚未编写的库中声明的虚拟方法内部:
    // Library written by BarCorp
    public abstract class Bar
    {
    // Derived class is responsible for initializing x.
    protected int x;
    protected abstract void InitializeX();
    public void M()
    {
    InitializeX();
    Console.WriteLine(x);
    }
    }

    编译这个库会出错吗?如果是,BarCorp 应该如何修复该错误?通过为 x 分配一个默认值?但这就是编译器所做的。

    假设这个库是合法的。如果 FooCorp 写
    public class Foo : Bar
    {
    protected override void InitializeX() { }
    }

    这是一个错误吗? 编译器应该如何解决这个问题? 唯一的方法是进行整个程序分析,跟踪程序中每条可能路径上每个字段的初始化静态,包括涉及在运行时选择虚拟方法的路径。这个问题可以任意难度;它可能涉及数百万条控制路径的模拟执行。分析本地控制流需要几微秒,并取决于方法的大小。分析全局控制流可能需要几个小时,因为这取决于程序和所有库中每个方法的复杂性。

    那么为什么不做一个不需要分析整个程序的更便宜的分析,只是更严重地高估呢?好吧,提出一种有效的算法,不会使编写实际编译的正确程序变得太难,设计团队可以考虑一下。我不知道任何这样的算法。

    现在,评论者建议“要求构造函数初始化所有字段”。这不是一个坏主意。事实上,这是一个不错的主意, C# 已经为结构提供了该功能 . struct 构造函数需要在 ctor 正常返回时明确分配所有字段;默认构造函数将所有字段初始化为其默认值。

    上课呢?那么, 你怎么知道构造函数已经初始化了一个字段 ? ctor 可以调用一个虚方法来初始化字段,现在我们又回到了之前的位置。结构没有派生类;类可能。包含抽象类的库是否需要包含初始化其所有字段的构造函数?抽象类如何知道字段应该初始化为什么值?

    John 建议简单地禁止在初始化字段之前调用 ctor 中的方法。所以,总结一下,我们的选择是:
  • 使常见的、安全的、常用的编程习语非法。
  • 进行昂贵的整个程序分析,使编译需要数小时才能查找可能不存在的错误。
  • 依靠自动初始化为默认值。

  • 设计团队选择了第三个选项。

    关于c# - 为什么局部变量需要初始化,而字段不需要?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30816496/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com