寫構造函數是一個反復的工作。很多開發人員都是先寫一個構造函數,然後 復制粘貼到其它的構造函數裡,以此來滿足類的一些重載接口。希望你不是這樣 做的,如果是的,就此停止吧。有經驗的C++程序可能會用一個輔助的私有方法 ,把常用的算法放在裡面來構造對象。也請停止吧。當你發現多重構造函數包含 相同的邏輯時,取而代之的是把這些邏輯放在一個常用的構造函數裡。你可以得 避免代碼的重復的好處,並且構造函數初始化比對象的其它代碼執行起來更高效 。C#編譯器把構造函數的初始化識別為特殊的語法,並且移除預置方法中重復的 變量和重復的基類構造函數。結果就是這樣的,你的對象最終執行最少的代碼來 合理的初始化對象。你同樣可以寫最少的代碼來把負責委托給一個常用的構造函 數。構造函數的預置方法充許一個構造函數調用另一個構造函數。這是一個簡單 的例子:
public class MyClass
{
// collection of data
private ArrayList _coll;
// Name of the instance:
private string _name;
public MyClass() :
this( 0, "" )
{
}
public MyClass( int initialCount ) :
this( initialCount, "" )
{
}
public MyClass( int initialCount, string name )
{
_coll = ( initialCount > 0 ) ?
new ArrayList( initialCount ) :
new ArrayList();
_name = name;
}
}
C#不支持帶默認值的參數,C++是很好 的解決這個問題的(譯注:C++可以讓參數有默認的值,從而有效的減少函數的重 載)。你必須重寫每一個特殊的構造函數。對於這樣的構造函數,就意味著大量 的代碼重復工作。可以使用構造函數鏈來取代常規的方法。下面就是一些常規的 低效率的構造函數邏輯:
public class MyClass
{
// collection of data
private ArrayList _coll;
// Name of the instance:
private string _name;
public MyClass ()
{
commonConstructor( 0, "" );
}
public MyClass( int initialCount )
{
commonConstructor( initialCount, "" );
}
public MyClass( int initialCount, string Name )
{
commonConstructor( initialCount, Name );
}
private void commonConstructor( int count,
string name )
{
_coll = (count > 0 ) ?
new ArrayList(count) :
new ArrayList();
_name = name;
}
}
這個版本看上去是一樣的,但生成的效率遠不及對象的其它代碼 。為了你的利益,編譯器為構造函數添加了一些代碼。添加了一些代碼來初始化 所有的變量(參見原則12)。它還調用了基類的構造函數。當你自己寫一些有效的 函數時,編譯器就不會添加這些重復的代碼了。第二個版本的IL代碼和下面寫的 是一樣的:
// Not legal, illustrates IL generated:
public MyClass()
{
private ArrayList _coll;
private string _name;
public MyClass( )
{
// Instance Initializers would go here.
object(); // Not legal, illustrative only.
commonConstructor( 0, "" );
}
public MyClass (int initialCount)
{
// Instance Initializers would go here.
object(); // Not legal, illustrative only.
commonConstructor( initialCount, "" );
}
public MyClass( int initialCount, string Name )
{
// Instance Initializers would go here.
object(); // Not legal, illustrative only.
commonConstructor( initialCount, Name );
}
private void commonConstructor( int count,
string name )
{
_coll = (count > 0 ) ?
new ArrayList(count) :
new ArrayList();
_name = name;
}
}
如果你用第一個版本寫構造函數,在編譯看來,你是這樣寫的:
// Not legal, illustrates IL generated:
public MyClass()
{
private ArrayList _coll;
private string _name;
public MyClass( )
{
// No variable initializers here.
// Call the third constructor, shown below.
this( 0, "" ); // Not legal, illustrative only.
}
public MyClass (int initialCount)
{
// No variable initializers here.
// Call the third constructor, shown below.
this( initialCount, "" );
}
public MyClass( int initialCount, string Name )
{
// Instance Initializers would go here.
object(); // Not legal, illustrative only.
_counter = initialCount;
_name = Name;
}
}
不同 之處就是編譯器沒有生成對基類的多重調用,也沒有復制實例變量到每一個構造 函數內。實際上基類的構造函數只是在最後一個構造函數裡被調用了,這同樣很 重要:你不能包含更多的構造函數預置方法。在這個類裡,你可以用this()把它 委托給另一個方法,或者你可以用base()調用基類的構造。但你不能同時調用兩 個。
還不清楚構造函數預置方法嗎?那麼考慮一下只讀的常量,在這個 例子裡,對象的名字在整個生命期內都不應該改變。這就是說,你應該把它設置 為只讀的。如果使用輔助函數來構造對象就會得到一個編譯錯誤:
public class MyClass
{
// collection of data
private ArrayList _coll;
// Number for this instance
private int _counter;
// Name of the instance:
private readonly string _name;
public MyClass()
{
commonConstructor( 0, "" );
}
public MyClass( int initialCount )
{
commonConstructor( initialCount, "" );
}
public MyClass( int initialCount, string Name )
{
commonConstructor( initialCount, Name );
}
private void commonConstructor( int count,
string name )
{
_coll = (count > 0 ) ?
new ArrayList(count) :
new ArrayList();
// ERROR changing the name outside of a constructor.
_name = name;
}
}
C++程 序會把這個_name留在每一個構造函數裡,或者通常是在輔助函數裡把它丟掉。 C#的構造函數預置方法提供了一個好的選擇,幾乎所有的瑣碎的類都包含不只一 個構造函數,它們的工作就是初始化對象的所有成員變量 。這是很常見的,這 些函數在理想情況下有相似的共享邏輯結構。使用C#構造預置方法來生成這些常 規的算法,這樣就只用寫一次也只執行一次。
這是C#裡的最後一個關於 對象構造的原則,是時候復習一下,一個類型在構造時的整個事件順序了。你須 要同時明白一個對象的操作順序和默認的預置方法的順序。你構造過程中,你應 該努力使所有的成員變量只精確的初始化一次。最好的完成這個目標的方法就是 盡快的完成變量的初始化。這是某個類型第一次構造一個實例時的順序:
1、靜態變量存儲位置0。
2、靜態變量預置方法執行。
3 、基類的靜態構造函數執行。
4、靜態構造函數執行。
5、實例變 量存儲位置0。
6、實例變量預置方法執行。
7、恰當的基類實例 構造函數執行。
8、實例構造函數執行。
後續的同樣類型的實例 從第5步開始,因為類的預置方法只執行一次。同樣,第6和第7步是優化了的, 它可以讓編譯器在構造函數預置方法上移除重復的指令。
C#的編譯器保 證所有的事物在初始化使用同樣的方法來生成。至少,你應該保證在你的類型創 建時,對象占用的所有內存是已經置0的。對靜態成員和實例成員都是一樣的。 你的目標就是確保你希望執行的初始化代碼只執行一次。使用預置方法來初始化 簡單的資源,使用構造函數來初始化一些具有復雜邏輯結構的成員。同樣,為了 減少重復盡可能的組織調用其它的構造函數。
返回教程目錄