13

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

为什么只分配局部变量?

在以下示例中:

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);
    }
}

为什么只给我一个警告并a给我一个错误?bc

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

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

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

4

3 回答 3

26

蒂姆对你的第一个问题给出了一个很好的答案,但我可以添加更多细节。

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

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

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

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

于 2013-01-19T22:05:18.630 回答
21

因为其他变量是用它们的默认值初始化的。

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

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

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

是相关的C# 语言规范:

5.3 明确赋值

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

5.3.1 初始赋值变量

以下类别的变量被归类为最初指定的:

  • 静态变量。

  • 类实例的实例变量。

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

  • 数组元素。

  • 值参数。

  • 参考参数。

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

5.3.2 最初未赋值的变量

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

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

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

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

于 2013-01-19T21:42:43.557 回答
9

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 章中有详细说明

明确的分配检查有其局限性。它适用于局部变量,因为它们的范围非常有限(方法体私有)并且您无法使用反射来获取它们。处理类的字段要困难得多,它需要整个程序分析,这可能需要超出编译器所能看到的范围。就像另一个程序集中的代码一样。这就是为什么 C# 编译器只能发出警告但不能直接拒绝代码的原因。

于 2013-01-19T22:22:17.070 回答