# 原則12:選擇變量初始化語法(initializer)而不是賦值語句
**By D.S.Qiu**
尊重他人的勞動,**支持原創,轉載請注明****[出處](/blog/1987663)****:[http://dsqiu.iteye.com](http://dsqiu.iteye.com)**
類經常會有多個構造函數。時間一長,成員變量和構造器很難保持同步。避免這個的最好方式就是在變量聲明的時候初始化而不是在構造函數內初始化。你應該使用初始化語法為靜態變量和實例變量進行初始化。
在 C# 中,當你聲明變量的時候構建變量是很自然的。當你聲明變量時直接進行初始化:
```
public class MyClass
{
// declare the collection, and initialize it.
private List<string> labels = new List<string>();
}
```
無論 MyClass 類有多少個構造函數, labels 都會被正確初始化。編譯器會在構造器的開頭為你定義的實例成員變量生成初始化代碼。當你增加一個構造函數, labels 就會獲得初始化。同樣的,如果你增加一個新的成員變量,你不需要在每個構造函數中添加初始化代碼,在定義的地方初始化變量是非常合適的。同樣重要的是,初始化語句(initializer①)也會被添加進編譯器產生的默認構造函數中。當你的類沒有顯式定義任何構造函數編譯器會創建一個默認構造函數。
初始化語句比構造函數中的語句更方便快捷。初始化語句被放在你的構造函數之前。初始化語句在基類構造函數之前執行,而且它們執行的順序是定義它們的順序。
使用初始化語句是你的類中成員變量未初始化最簡單的方法,但不完美。有三種情況,你不應該使用初始化語句語法。第一種情況是當你要將對象初始化為0或 null ,系統在你的代碼執行之前,會默認為所有內容設置為0。系統產生的0初始化是使用非常低級的 CPU 指令設置整塊內存為0。你的任何額外的0初始化都是多余的。 C# 編譯器會非常盡責地添加額外指令去又一次設置內存為0。這沒有問題——只是低效的。實際上,如果是值類型,那會更低效。
```
MyValType myVal1; // initialized to 0
MyValType myVal2 = new MyValType(); // also 0
```
上面兩條語句的變量都會初始化為0。第一個是設置 myVal1 的內存為0。第二個是使用 IL 指令 initObj ,這個會引起 myVal2 的封箱和拆箱操作。這需要相當的額外時間(查看原則45)。
當你對同一個對象進行多次初始化時,第二種情況就來了。只有變量在所有構造函數中接收的初始化代碼是一樣的才使用初始化語句語法。下面版本的 MyClass 構造函數呢中創建兩個不同的 List 對象:
```
public class MyClass2
{
// declare the collection, and initialize it.
private List<string> labels = new List<string>();
MyClass2()
{
}
MyClass2(int size)
{
labels = new List<string>(size);
}
}
```
當你創建 MyClass2 對象,并指定了集合的大小,你就創建了兩個數組隊列。其中一個立即變為垃圾。變量初始化語句在每個構造函數之前執行。構造函數中創建了第二個數組隊列。編譯器產生了這樣的 MyClass2 版本,當然你是絕不會手動這樣寫的。(查看原則14,使用恰當方式處理這個問題)。
```
public class MyClass2
{
// declare the collection, and initialize it.
private List<string> labels;
MyClass2()
{
labels = new List<string>();
}
MyClass2(int size)
{
labels = new List<string>();
labels = new List<string>(size);
}
}
```
如果你使用隱式屬性也是會出現同樣的情況(查看原則1)。你的代碼不能訪問編譯生成的備份存儲域,所以隱式屬性就不能使用初始化語句。除了在構造函數匯總初始化隱式屬性,你別無選擇。使用隱式屬性仍然具有的一個好處就是在 set 訪問器中不用進行邏輯校驗。如果你把隱式屬性遷移為命名的備份存儲域,你應該使用初始化語法而不是構造函數來更新初始化代碼。因為使用隱式屬性是數據成員的更好選擇,原則14說明了如何減少隱式屬性數據域的復制。
要在構造函數中初始化的最后一個原因是方便異常的處理。你不能再初始化語句中使用 try 塊。初始化成員變量時的任何異常都會傳給實例化的對象。你不能在類內對這個異常進行捕獲。你需要把初始化代碼移到構造函數中,這樣就可以實現正確的回收代碼來創建你的類型和妥善處理異常(查看原則47)。
成員變量初始化語法是保證是初始化的無論哪個構造函數被調用的最簡單方法。初始化語法都在每個構造函數之前執行。使用這個語法意味著你不會因新版本中添加的構造函數而忘記初始化。當所有構造函數以相同方式創建成員變量時,使用初始化語法;這易于閱讀,更易于維護。
①:initializer 翻譯為初始化語法
小結:
昨晚一直狀態不好,就沒有寫下去,還好這個原則比較簡短,就借用早餐+午休的時間完成了。這個原則建議我們要使用初始化語法來初始化成員變量,這樣既簡單又便于維護,不過除了上面說的三種情況之外。
歡迎各種不爽,各種噴,寫這個純屬個人愛好,秉持”分享“之德!
有關本書的其他章節翻譯請[點擊查看](/category/297763),轉載請注明出處,尊重原創!
如果您對D.S.Qiu有任何建議或意見可以在文章后面評論,或者發郵件(gd.s.qiu@gmail.com)交流,您的鼓勵和支持是我前進的動力,希望能有更多更好的分享。
轉載請在**文首**注明出處:[http://dsqiu.iteye.com/blog/1987663](/blog/1987663)
更多精彩請關注D.S.Qiu的博客和微博(ID:靜水逐風)
- 第一章 C# 語言習慣
- 原則1:使用 屬性(Poperty)代替可直接訪問的數據成員(Data Member)
- 原則2:偏愛 readonly 而不是 const
- 原則3:選擇 is 或 as 而不是強制類型轉換
- 原則4:使用條件特性(conditional attribute)代替 #if
- 原則5:總是提供 ToString()
- 原則6:理解幾個不同相等概念的關系
- 原則7:明白 GetHashCode() 的陷阱
- 原則8:優先考慮查詢語法(query syntax)而不是循環結構
- 原則9:在你的 API 中避免轉換操作
- 原則10:使用默認參數減少函數的重載
- 原則11:理解小函數的魅力
- 第二章 .NET 資源管理
- 原則12:選擇變量初始化語法(initializer)而不是賦值語句
- 原則13:使用恰當的方式對靜態成員進行初始化
- 原則14:減少重復的初始化邏輯
- 原則15:使用 using 和 try/finally 清理資源
- 原則16:避免創建不需要的對象
- 原則17:實現標準的 Dispose 模式
- 原則17:實現標準的 Dispose 模式
- 原則18:值類型和引用類型的區別
- 原則19:確保0是值類型的一個有效狀態
- 原則20:更傾向于使用不可變原子值類型
- 第三章 用 C# 表達設計
- 原則21:限制你的類型的可見性
- 原則22:選擇定義并實現接口,而不是基類
- 原則23:理解接口方法和虛函數的區別
- 原則24:使用委托來表達回調
- 原則25:實現通知的事件模式
- 原則26:避免返回類的內部對象的引用
- 原則27:總是使你的類型可序列化
- 原則28:創建大粒度的網絡服務 APIs
- 原則29:讓接口支持協變和逆變
- 第四章 和框架一起工作
- 原則30:選擇重載而不是事件處理器
- 原則31:用 IComparable&lt;T&gt; 和 IComparer&lt;T&gt; 實現排序關系
- 原則32:避免 ICloneable
- 原則33:只有基類更新處理才使用 new 修飾符
- 原則34:避免定義在基類的方法的重寫
- 原則35:理解 PLINQ 并行算法的實現
- 原則36:理解 I/O 受限制(Bound)操作 PLINQ 的使用
- 原則37:構造并行算法的異常考量
- 第五章 雜項討論
- 原則38:理解動態(Dynamic)的利與弊
- 原則39:使用動態對泛型類型參數的運行時類型的利用
- 原則40:使用動態接收匿名類型參數