为什么派生类字段在基类字段之前初始化

  • 本文关键字:字段 基类 初始化 派生 c#
  • 更新时间 :
  • 英文 :


我知道以下是创建派生类对象时数据成员初始化和构造函数调用的顺序

  1. 派生静态字段
  2. 派生静态构造函数
  3. 派生实例字段
  4. 基本静态字段
  5. 基本静态构造函数
  6. 基本实例字段
  7. 基实例构造函数
  8. 派生实例构造函数

这里的一切似乎都很合乎逻辑,除了一件事:
我不明白为什么派生类字段在基类字段之前初始化?老实说,在浏览了互联网并仔细思考后,我找不到任何有效和合乎逻辑的解释。

考虑以下类结构:

Animal:

public class Animal
{
    public string Name;
    public Animal(string name)
    {
        Name = name;
    }
}

Dog(源自Animal(:

public class Dog : Animal
{
    static string DogName = "Dog";
    public Dog() : base(DogName)
    {
                
    }
}

Cat(也是从Animal派生而来(:

public class Cat : Animal
{
    public Cat() : base(nameof(Cat))
    {
    }
}       

如果我们想实例化DogCat的副本,它们最终都将调用Animal构造函数。Animal构造函数需要将stringname作为参数传入。为了从CatDog传入name参数,需要在某个地方进行定义。因此,它必须在调用Animal构造函数之前进行定义,但编译器在尝试初始化派生类之前不知道该怎么做。因此,最终,它需要初始化派生类中的至少一些值,以便使用它们来调用基类的构造函数。

对于Dog,它将初始化静态DogName,以便将其传递到基类的构造函数中,而对于Cat,编译器将评估nameof(Cat),以便传递到Animal构造函数中。


为了理解为什么初始化其中一些值是有意义的,让我们假设我们是编译器,试图创建一个新的Dog(将这一切视为伪代码——用于描述逻辑的单词,而不是符合当前语言的语法——以下不是C#(:

var dog = new Dog():

  1. 初始化Dog中我们已经知道值的所有变量(以防我们在构建过程中需要它们(:

    static string DogName = "Dog";

  2. 检查构造函数,发现我们实际上需要构造Dog基类Animal:

    var animal = new Animal(string name)<-这是我们在DogName 中经过的地方

  3. 初始化Animal中我们已经知道值的所有变量(以防我们在构建过程中需要它们(:

    public string Name;

*(然后我们将重复步骤2和3,直到我们达到基本的Object类,然后我们构建它,在这种情况下开始我们的Animal的基础(

  1. 使用传入的name构建我们的Animal,即DogName(来自步骤2(

    Name = name;

  2. Animal:构建我们的Dog

    Dog = fresh instance of Animal(我们在Dog构造函数中没有其他事情要做(


如果我们跳过步骤1,那么必须一直返回到实际为name创建原始值的类,这将有点烦人。

在一个更复杂的例子中,如果我们还没有它,我们需要回到上一个类,看看构造函数中作为参数传入了什么。如果这个类没有它,我们将不得不对每个类重复这个过程,一直到初始化引用值的原始类,然后一直到我们正在查看的当前构造函数

尽管这会奏效,但从某种意义上说,它也让我们倒退了一步。更方便的做法是逐步完成我们需要的所有类,并确保我们在这一过程中拥有我们可能需要的所有值,然后一旦我们走到了路的尽头,我们就可以一次性返回。


添加一些有趣的内容作为注意事项,即当您有一个成员变量在初始化时,它将永远初始化。如果发生这种情况,则会出现堆栈溢出错误,因为托管值列表变得不可管理,因为您试图在继续基类的同时保留所有初始化的值。

例如,如果您尝试创建以下C#类的实例:

public class StackOverflow
{
    public StackOverflow stackOverflow = new StackOverflow();
    public StackOverflow(){ }
}

从逻辑上讲,您可能希望在构造函数中使用初始化的值。

参见:

public class Base
{
    private int x = 3;
    public Base()
    {
        Print();
    }
    protected virtual void Print()
    {
        Console.WriteLine(x);
    }
}
public class Derived : Base
{
    private int x = 5;
    protected override void Print()
    {
        base.Print();
        Console.WriteLine(x);
    }
}

注意:这正是您不应该从构造函数调用虚拟方法的原因,因为您的对象可能没有完全初始化。

最新更新