如果我在类中创建一个bool变量,比如bool check
,它的默认值是false。
但是当我在方法中创建相同的bool变量,用bool check
(而不是在类内部创建),就会出现错误"使用了未赋值的本地变量check"。为什么会这样呢?
如果我在类中创建一个bool变量,比如bool check
,它的默认值是false。
但是当我在方法中创建相同的bool变量,用bool check
(而不是在类内部创建),就会出现错误"使用了未赋值的本地变量check"。为什么会这样呢?
Yuval和David的回答基本是正确的; 总结如下:
David的答案中的评论者问为什么无法通过静态分析检测未赋值字段的使用; 这是我想在这个答案中扩展的重点。
首先,对于任何变量,本地的或其他的,实际上很难准确确定变量是否已赋值或未赋值。考虑:
bool x;
if (M()) x = true;
Console.WriteLine(x);
问题“x是否被赋值?”等同于“M()是否返回true?”假设M()返回true,当且仅当费马大定理对eleventy gajillion以下的所有整数成立,否则返回false。为了确定x是否一定被分配,编译器必须基本上证明费马大定理。编译器并不那么聪明。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#已经为结构体具有了这个功能。结构体构造函数需要在构造函数正常返回之前明确分配所有字段;默认构造函数将所有字段初始化为它们的默认值。
那类呢?那么您如何知道构造函数是否已经初始化了字段呢?构造函数可以调用虚方法来初始化字段,现在我们回到了之前的位置。结构体没有派生类;类可能有。包含抽象类的库是否需要包含初始化其所有字段的构造函数?抽象类如何知道应将字段初始化为哪些值?
John建议在字段初始化之前禁止调用构造函数中的方法。因此,总结一下,我们的选项是:
设计团队选择了第三个选项。
bool x;
等同于 bool x = false;
即使在方法内部? - durron597为什么这个不适用于类字段?我想这是因为需要划定一个界限,而本地变量的初始化要容易得多,也更容易诊断和正确处理,而不是类字段。编译器可能会这样做,但想想它需要进行所有可能的检查(其中一些与类代码本身无关),以评估类中每个字段是否已初始化。我不是编译器设计师,但我确定它肯定会更难,因为有很多情况要考虑,并且必须及时完成。对于每个功能,都需要设计、编写、测试和部署,而实施这一点所需的价值与付出的努力相比是不值得且复杂的。
string str;
var len1 = str.Length;
var array = new string[10];
var len2 = array[0].Length;
第二行代码无法编译,因为它试图读取未初始化的字符串变量。但第四行代码可以编译通过,因为array
已经被初始化,但仅使用默认值。由于字符串的默认值是null,在运行时我们会得到异常。在Stack Overflow上花费时间的任何人都将知道这种显式/隐式初始化不一致性会导致许多“为什么我会收到“对象引用未设置为对象实例”的错误?”的问题。
f
时生成错误。它将在编译构造函数时生成。如果您让一个构造函数带有可能未初始化的字段,那就是一个错误。在所有字段初始化之前调用类方法和getter也可能有限制。 - John Kugelman以上回答很好,但我想为那些懒得读长篇回答(比如我)的人发布一个更简单/更短的答案。
class Foo {
private string Boo;
public Foo() { /** bla bla bla **/ }
public string DoSomething() { return Boo; }
}
属性Boo
可能在构造函数中已经初始化,也可能没有。因此,当它找到return Boo;
时,并不会假设它已经被初始化。它只是抑制了错误。
public string Foo() {
string Boo;
return Boo; // triggers error
}
{ }
字符定义了代码块的范围。编译器遍历这些 { }
块的分支,跟踪相关内容。它可以轻松地知道 Boo
没有初始化。然后就会触发错误。
引入这个错误是为了减少制作源代码时所需的行数,使其更加安全。如果没有这个错误,上面的代码将会变成这样:
public string Foo() {
string Boo;
/* bla bla bla */
if(Boo == null) {
return "";
}
return Boo;
}
根据手册:
C#编译器不允许使用未初始化的变量。如果编译器检测到可能未被初始化的变量的使用,它将生成编译器错误CS0165。有关更多信息,请参阅字段(C#编程指南)。请注意,即使您的特定代码没有,当编译器遇到可能导致使用未分配变量的结构时,也会生成此错误。这避免了过于复杂的确定赋值规则的必要性。