程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> Effective C#原則14:使用構造函數鏈

Effective C#原則14:使用構造函數鏈

編輯:關於C#

寫構造函數是一個反復的工作。很多開發人員都是先寫一個構造函數,然後 復制粘貼到其它的構造函數裡,以此來滿足類的一些重載接口。希望你不是這樣 做的,如果是的,就此停止吧。有經驗的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的。對靜態成員和實例成員都是一樣的。 你的目標就是確保你希望執行的初始化代碼只執行一次。使用預置方法來初始化 簡單的資源,使用構造函數來初始化一些具有復雜邏輯結構的成員。同樣,為了 減少重復盡可能的組織調用其它的構造函數。

返回教程目錄

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved