Effective C# 原則12:選擇變數初始化而不是指派陳述式

來源:互聯網
上載者:User

Effective C# 原則12:選擇變數初始化而不是指派陳述式
Item 12: Prefer Variable Initializers to Assignment Statements

(譯註:根據我個人對文章的理解,我把initializer譯為:初始化器,它是指初始化文法,也就是在一個類裡聲明變數的同時,直接建立執行個體值的方法。
例:object m_o = new object();如果這段代碼不在任何函數內,但在一個類裡,它就是一個初始化器,而不管你是把它放在類的開始還以結尾。)

一些類經常不只一個建構函式。時間一長,就難得讓它的成員變數以及建構函式進行同步了。最好的確保這樣的事不會發生的方法就是:在聲明就是的時間就直接初始化,而不是在每個建構函式內進行賦值。而且你應該使用初始化器文法同時為靜態和執行個體的變數進行初始化。

在C#裡,當你聲明一個變數時就自然的構造了這個成員變數。直接賦值:
public class MyClass
{
  // declare the collection, and initialize it.
  private ArrayList _coll = new ArrayList( );
}

忽略你最終會給MyClass添加多少個建構函式,_coll會正確的初始化。編譯器會產生一些代碼,使得在你的任何一個建構函式調用前,都會初始化你聲明的執行個體變數。當你添加一個新的建構函式時,_coll就給你初始化了。當你添加了一個新的變數,你不用在所有的建構函式裡添加初始化代碼;直接在聲明的地方對它進行初始化就行了。同樣重要的是:如果你沒有明確的聲明任何一個建構函式,編譯會預設的給你添加一個,並且把所有的變數初始化過程都添加到這個建構函式裡。

初始化器更像是一個到建構函式的方便的快捷方法。初始化產生的程式碼會放置在類型的建構函式之前。初始化會在執行類型的基類的建構函式之前被執行,並且它們是按你聲明的先後關係順序執行的。

使用初始化器是一個最簡單的方法,在你的類型裡來避免使用一些沒有賦值的變數,但這並不是很好。下面三種情況下,你不應該使用初始化器文法。首先就是,如果你是初始化一個對象為0,或者為null。系統預設會在你任何代碼執行前,為所有的內容都初始化為0。系統置0的初始化是基於底層的CPU指令,對整個記憶體塊設定。你的任何其它置0的初始化語句是多餘的。C#編譯器忠實的添加額外的指令把記憶體設定為0。這並沒有錯,只是效率不高。事實上,如果是處理實值型別資料,這是很不值的:

MyValType _MyVal1;  // initialized to 0
MyValType _MyVal2 = new MyValType(); // also 0

兩條語句都是把變數置為0。第一個是通過設定包含_MyVal1的記憶體來置0;而第二個是通過IL指令initobj,這對變數_MyVal2會產生裝箱與拆箱操作。這很要花一點額外的時間(參見原則17)。

第二個低效率的是在你為一個對象添加兩個建構函式時會產生。你使用初始化器初始設定變數,而所有的建構函式也對這些變數進行了初始化。這個版本的MyClass兩個不同的ArrayList對象在它的建構函式內:

public class MyClass
{
  // declare the collection, and initialize it.
  private ArrayList _coll = new ArrayList( );

  MyClass( )
  {
  }

  MyClass( int size )
  {
    _coll = new ArrayList( size );
  }
}

當你建立一個新的MyClass對象時,特別指定集合的大小,你建立了兩個數組列表。其中一個很快成為垃圾對象。初始化器在所有的建構函式之前會執行,建構函式會建立第2個數組列表。編譯器產生了這個的一個版本,當然這是你決不會手動寫出來的。(參見原則14來使用一個恰當的方法來解決這個問題)

public class MyClass
{
  // declare the collection, and initialize it.
  private ArrayList _coll;

  MyClass( )
  {
    _coll = new ArrayList( );
  }

  MyClass( int size )
  {
    _coll = new ArrayList( );
    _coll = new ArrayList( size );
  }
}

最後一個原因要把初始化放到建構函式裡就是促使異常的捕獲。你不能在初始化器中使用try塊,任何在構造時因成員變數產生的異常可能衍生到對象的外面。你無法試圖在你的類裡來捕獲它。你應該把那些初始化代碼移到建構函式裡,這樣你就可以捕獲異常從而保證你的代碼很友好(參見原則45)。

變數初始化器是一個最簡單的方法,在忽略建構函式時來保證成員變數被正確的初始化。初始化器在所有的建構函式之前被執行。使用這樣的文法意味著當你在為後來發布的版本中添加了建構函式時,不會忘記添加恰當的初始化到建構函式裡。當建構函式與初始化產生同樣的成員對象時,就使用初始化器。閱讀簡單而且易於維護。

Item 12: Prefer Variable Initializers to Assignment Statements
Classes often have more than one constructor. Over time, it's easy for the member variables and the constructors to get out of synch. The best way to make sure this doesn't happen is to initialize variables where you declare them instead of in the body of every constructor. You should utilize the initializer syntax for both static and instance variables.

Constructing member variables when you declare that variable is natural in C#. Just assign a value:

public class MyClass
{
  // declare the collection, and initialize it.
  private ArrayList _coll = new ArrayList( );
}

 

Regardless of the number of constructors you eventually addto the MyClass type, _coll will be initialized properly. The compiler generates code at the beginning of each constructor to execute all the initializers you have defined for your instance member variables. When you add a new constructor, _coll gets initialized. Similarly, if you add a new member variable, you do not need to add initialization code to every constructor; initializing the variable where you define it is sufficient. Equally important, the initializers are added to the compiler-generated default constructor. The C# compiler creates a default constructor for your types whenever you don't explicitly define any constructors.

Initializers are more than a convenient shortcut for statements in a constructor body. The statements generated by initializers are placed in object code before the body of your constructors. Initializers execute before the base class constructor for your type executes, and they are executed in the order the variables are declared in your class.

Using initializers is the simplest way to avoid uninitialized variables in your types, but it's not perfect. In three cases, you should not use the initializer syntax. The first is when you are initializing the object to 0, or null. The default system initialization sets everything to 0 for you before any of your code executes. The system-generated 0 initialization is done at a very low level using the CPU instructions to set the entire block of memory to 0. Any extra 0 initialization on your part is superfluous. The C# compiler dutifully adds the extra instructions to set memory to 0 again. It's not wrongit's just inefficient. In fact, when value types are involved, it's very inefficient.

MyValType _MyVal1;  // initialized to 0
MyValType _MyVal2 = new MyValType(); // also 0

 

Both statements initialize the variable to all 0s. The first does so by setting the memory containing MyVal1 to 0. The second uses the IL instruction initobj, which causes both a box and an unbox operation on the _MyVal2 variable. This takes quite a bit of extra time (see Item 17).

The second inefficiency comes when you create multiple initializations for the same object. You should use the initializer syntax only for variables that receive the same initialization in all constructors. This version of MyClass has a path that creates two different ArrayList objects as part of its construction:

public class MyClass
{
  // declare the collection, and initialize it.
  private ArrayList _coll = new ArrayList( );

  MyClass( )
  {
  }

  MyClass( int size )
  {
    _coll = new ArrayList( size );
  }
}

 

When you create a new MyClass, specifying the size of the collection, you create two array lists. One is immediately garbage. The variable initializer executes before every constructor. The constructor body creates the second array list. The compiler creates this version of MyClass, which you would never code by hand. (For the proper way to handle this situation, see Item 14.)

public class MyClass
{
  // declare the collection, and initialize it.
  private ArrayList _coll;

  MyClass( )
  {
    _coll = new ArrayList( );
  }

  MyClass( int size )
  {
    _coll = new ArrayList( );
    _coll = new ArrayList( size );
  }
}

 

The final reason to move initialization into the body of a constructor is to facilitate exception handling. You cannot wrap the initializers in a TRy block. Any exceptions that might be generated during the construction of your member variables get propagated outside of your object. You cannot attempt any recovery inside your class. You should move that initialization code into the body of your constructors so that you implement the proper recovery code to create your type and gracefully handle the exception (see Item 45).

Variable initializers are the simplest way to ensure that the member variables in your type are initialized regardless of which constructor is called. The initializers are executed before each constructor you make for your type. Using this syntax means that you cannot forget to add the proper initialization when you add new constructors for a future release. Use initializers when all constructors create the member variable the same way; it's simpler to read and easier to maintain.
 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.