为什么派生类的字段初始值设定项在基类的初始值设定项之前执行

时间:2011-08-18 11:09:45

标签: c# oop inheritance

构造函数按从上到下的顺序执行。 base首先跟随派生的。这个安排基于一个重要的OOP保证,即一个对象(这里是base)必须始终在它被使用之前被初始化(这里是派生类的构造函数)。

我想知道为什么字段初始化程序在C#中不遵循这个原则?我在这里错过了什么吗?

我也发现了使用字段初始化器的这个原理的有用性。我有一个基类,其属性返回Identity对象。每个派生类都有自己的存储库字段,我一直使用field-initializer初始化(使用默认构造函数)。最近我决定还必须为存储库类提供Identity对象,因此我在存储库构造函数中引入了一个额外的参数。但我很难找到:

public class ForumController : AppControllerBase
{
        ForumRepository repository = new ForumRepository(Identity);
    // Above won't compile since Identity is in the base class.

   // ... Action methods.
}

现在我只剩下一个选项,即使用默认构造函数来填充我的每个控制器,仅用于使用Identity初始化存储库对象。

4 个答案:

答案 0 :(得分:6)

答案 1 :(得分:2)

在C#中,构造函数运行序列:

  Perform all field initializers
  Chain to base class constructor
  Execute user-supplied code for constructor

在vb.net中,序列为:

  Chain to base class constructor
  Perform all field initializers
  Execute user-supplied code for constructor

没有基于框架的原因,为什么C#按照它的顺序执行操作,vb.net可以并且确实以不同的顺序执行它们。 C#方法的设计理由是,在所有字段初始化程序(对于派生字段和基类字段)运行之前,不应该有对象暴露给外部世界;由于基类构造函数可以将对象暴露给外部世界,因此强制执行该需求意味着字段初始值设定项必须在基类构造函数之前运行。

就个人而言,我认为这种理由并不特别令人信服。在许多情况下,如果没有在基本构造函数运行之前不可用的信息,则无法将字段设置为有用值。任何基本构造函数可能暴露部分构造的实例的代码都需要为这种可能性做好准备。虽然有时候指定一个字段初始化程序应该“早期”运行是有用的,但我认为还有很多情况下它们有助于它们能够访问fledgeling对象(在某种程度上因为我相信在类实例的生命周期中其值应被视为不变量的类字段应该在实际时通过初始化器以声明方式设置,而不是在构造函数中强制设置。)

顺便提一下,我希望在vb.net和C#中看到的一个功能是声明参数初始化字段和伪字段的方法。如果某个类具有某个名称和类型的参数初始化字段,则该类的每个不链接到同一个类的另一个构造函数必须包含具有相应名称和类型的参数。在完成任何其他操作之前,将在构造函数中设置这些字段的值,并且其他字段初始值设定项可以访问这些值。伪字段在语法上的行为类似于字段,除了它们在字段初始化器中可用,并且将在构造函数中实现为局部变量。这样的特征将使许多类型的结构更方便。例如,如果一个类型应该在其整个生命周期中保存一个特定的数组实例,那么可以说:

  readonly param int Length;
  readonly ThingType[] myArray = new ThingType[Length];

似乎比必须在类构造函数中构造数组(在基本构造函数运行之后才能发生)或(对于vb.net)必须将长度传递给基类构造函数更好。然后可以使用它来设置一个字段(这将占用类中的空间,即使它的值 - 如上面的Length - 可能是多余的。)

答案 2 :(得分:0)

字段初始化器在构造函数中执行,并且当首先调用基类中的构造函数时,所有字段初始化器也会在派生构造函数执行之前执行。

示例:

public class Base {

  // field initialiser:
  private string _firstName = "Arthur";

  public string FirstName { get { return _firstName;}}
  public string LastName { get; private set; }

  // initialiser in base constructor:    
  public Base() {
    LastName = "Dent";
  }

}

public class Derived : Base {

  public string FirstNameCopy { get; private set; }
  public string LastNameCopy { get; private set; }

  public Derived() {
    // get values from base class:
    FirstNameCopy = FirstName;
    LastNameCopy = LastName;
  }

}

测试:

Derived x = new Derived();
Console.WriteLine(x.FirstNameCopy);
Console.WriteLine(x.LastNameCopy);

输出:

Arthur
Dent

答案 3 :(得分:0)

字段初始化程序并不是构造函数的替代品。

根据MSDN文档,所有字段初始值设定项都在构造函数之前执行。但是对Field Initializer的限制是它们不能引用其他实例字段。 (http://msdn.microsoft.com/en-us/library/ms173118(v=vs.80).aspx

限制是由于无法识别正确的顺序以及在编译器级别执行Field Initializers的依赖关系。

你必须编写一个默认的构造函数来实现你想要的。但是,您可以尝试使用静态字段;这是一种不好的做法,可能甚至不适合您的设计。


编辑以澄清评论中提出的问题:

就派生类而言,无论是通过初始化程序还是构造函数初始化,基类字段看起来都是一样的。此信息无法提供给子类,因为这将意味着公开基类的实现(对于基类是严格私有的)。

这意味着派生类Initializer无法确定在访问它们之前是否已初始化所有基类字段。因此,不允许这种行为。