关于未分配的变量

只是好奇,我不是想解决任何问题。

为什么只应分配局部变量?

在以下示例中:

class Program { static int a; static int b { get; set; } static void Main(string[] args) { int c; System.Console.WriteLine(a); System.Console.WriteLine(b); System.Console.WriteLine(c); } } 

为什么ab给我一个警告而c给了我一个错误?

另外,为什么我不能只使用Value Type的默认值并编写以下代码?

  bool MyCondition = true; int c; if (MyCondition) c = 10; 

它与内存管理有什么关系吗?

因为其他变量使用其默认值进行初始化。

Jon Skeet已经在这个问题上找到了一些有趣的词 :

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

实例变量不是这种情况。 考虑一个简单的属性 – 你怎么知道有人会在它得到它之前设置它? 这使得执行合理规则基本上不可行 – 因此要么必须确保在构造函数中设置所有字段,要么允许它们具有默认值。 C#团队选择了后一种策略。

这是相关的C#语言规范:

5.3明确的任务

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

5.3.1最初分配的变量

以下类别的变量分类为最初分配的:

  • 静态变量。

  • 类实例的实例变量。

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

  • 数组元素。

  • 值参数。

  • 参考参数。

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

5.3.2最初未分配的变量

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

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

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

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

蒂姆给出了第一个问题的答案,但我可以补充一些细节。

你的第一个问题基本上是“当地人必须明确分配,所以为什么不对字段做出同样的限制?” 蒂姆指出,乔恩指出实际上很难这样做。 对于本地人来说,当首次阅读本地时以及首次写入本地时,它几乎总是清晰可见。 在不清楚的情况下,编译器可以进行合理的保守猜测。 但是对于字段,要知道第一次读取和第一次写入何时发生,您必须知道以何种顺序调用哪些方法的各种事项。

简而言之,分析当地人需要进行局部分析; 分析不必经过包含声明的块。 现场分析需要全球分析。 这是很多工作; 只是说字段初始化为默认值更容易。

(现在,也就是说,当然可以进行全局分析;我的新工作可能涉及正是进行这种分析。)

你的第二个问题基本上是“那么,如果默认值的自动分配对于字段来说足够好那么为什么它对当地人来说不够好呢?” 并且答案是“因为未能分配局部变量并且意外地获取默认值是错误的常见来源。” C#经过精心设计,不鼓励编程实践导致烦人的漏洞,这就是其中之一。

CLR提供了一种硬保证,即将局部变量初始化为其默认值。 但这种保证确实有局限性。 缺少的是它能够识别方法体内的范围块。 一旦编译器将代码转换为IL,它们就会消失。 Scope是一种语言结构,在CLI中没有并行,不能用IL表示。

你可以在像VB.NET这样的语言中看到这个问题。 这个人为的例子显示了这种行为:

 Module Module1 Sub Main() For ix = 1 To 3 Dim s As String If ix = 2 Then s = "foo" If s Is Nothing Then Console.WriteLine("null") Else Console.WriteLine(s) Next Console.ReadLine() End Sub End Module 

输出:

 null foo foo 

或者换句话说,局部变量s只初始化一次并在之后保留其值。 这当然有创造错误的诀窍。 VB.NET编译器确实为它生成一个警告,并且具有简单的语法来避免它(As New)。 像C ++ / CLI这样的托管语言具有相同的行为,但根本不会发出诊断信息。 但是C#语言提供了更强的保证,它会产生错误。

这条规则称为“明确分配”。 C#语言规范第5.3.3章详细解释了确切的规则

明确的赋值检查有其局限性。 它适用于局部变量,因为它们的范围非常有限(方法体专用)并且您无法使用Reflection获取它们。 更难处理类的字段,它需要整个程序分析,可能需要超出编译器可以看到的范围。 像另一个程序集中的代码。 这就是为什么C#编译器只能警告它但不能拒绝代码的权利。