看看下面這段代碼,你覺得它會輸出什麼呢?
class Foo
{
public Foo(string s)
{
Console.WriteLine("Foo constructor: {0}", s);
}
public void Bar(){}
}
class Base
{
readonly Foo baseFoo = new Foo("Base initializer");
public Base()
{
Console.WriteLine("Base constructor");
}
}
class Derived : Base
{
readonly Foo derivedFoo = new Foo("Derived initializer.");
public Derived()
{
Console.WriteLine("Derived constructor");
}
}
class Program
{
static void Main(string[] args)
{
new Derived();
}
}
先猜一下吧,似乎應該是“Base initializer, Base constructor, Derived initializer, Derived constructor”。
事實上,應當是先執行類成員的初始化,順序是從derived到base,然後是兩個構造函數,順序是從base從derived。
這種方式是很有意義的,在類繼承體系中層次較深的類(離System.Object較遠)將依賴於較淺的類(離System.Object較近)。但是很多人會相信調用的順序應當等價於下面的偽代碼:
// 期望的順序
BaseConstructor()
{
ObjectConstructor();
baseFoo = new Foo("Base initializer");
Console.WriteLine("Base constructor");
}
DerivedConstructor()
{
BaseConstructor();
derivedFoo = new Foo("Derived initializer");
Console.WriteLine("Derived constructor");
}
而實際情況則是:
// 實際的順序
BaseConstructor()
{
baseFoo = new Foo("Base initializer");
ObjectConstructor();
Console.WriteLine("Base constructor");
}
DerivedConstructor()
{
derivedFoo = new Foo("Derived initializer");
BaseConstructor();
Console.WriteLine("Derived constructor");
}
那麼,這樣處理是為什麼呢?
...
...
...
我們來看一下,如果代碼按期望的順序(第一段偽代碼)執行,會產生什麼問題:
class Base
{
public Base()
{
Console.WriteLine("Base constructor");
if (this is Derived) (this as Derived).DoIt();
// 如果是在創建Derived類的實例,就會遭遇null。
Blah();
// 如果是在創建MoreDerived類的實例,就會遭遇null。
}
public virtual void Blah() { }
}
class Derived : Base
{
readonly Foo derivedFoo = new Foo("Derived initializer");
public DoIt()
{
derivedFoo.Bar();
}
}
class MoreDerived : Derived
{
public override void Blah() { DoIt(); }
}
看Base類的構造函數,如果按期望的順序執行,那麼在Base方法執行時,Derived類的實例成員並沒有得到初始化,此時就會有NullReference異常了。
而按照實際執行的順序,所有的實例成員都能確保被完整地初始化:)
當然了,如果readonly字段是在構造函數中進行的,那麼上面的確保機制就不復存在了。