# 原則19:確保0是值類型的一個有效狀態
**By D.S.Qiu**
**尊重他人的勞動,支持原創,轉載請注明出處:[http://dsqiu.iteye.com](http://dsqiu.iteye.com)**
.NET 系統會將所有對象默認初始化為0。也沒有方法阻止其他程序創建值類型對象并初始化為0。請使你的類型保留默認值。
有一個特例是枚舉。創建不包含0的枚舉是可為之的。所有的枚舉都繼承自 System.ValueType 。枚舉的起始值是0,但是你可以修改這個行為:
```
public enum Planet
{
// Explicitly assign values.
// Default starts at 0 otherwise.
Mercury = 1,
Venus = 2,
Earth = 3,
Mars = 4,
Jupiter = 5,
Saturn = 6,
Neptune = 7,
Uranus = 8
// First edition included Pluto.
}
Planet sphere = new Planet();
```
sphere 等于0,這不是有效值。任何代碼都依賴于(正常)的事實,枚舉類型都限定于定義的枚舉集,所以就不能工作。如果你創建一個枚舉類,確保0是其中一個值。如果你在你的枚舉使用位模式,將0定義為所有其他屬性都缺失。
既然如此,你可以強迫所有使用者都顯示初始化值:
```
Planet sphere2 = Planet.Mars;
```
但很難構建包含這個類型的其他類型:
```
public struct ObservationData
{
private Planet whichPlanet; //what am I looking at?
private double magnitude; // perceived brightness.
}
```
使用者創建 ObservationData 對象會創建無效的 Planet 域:
```
ObservationData d = new ObservationData();
```
新創建的 ObservationData 有0的 magnitude,這是合理的。但是 wihchPlanet 是無效的。你需要使0是一個合理的值。如果可能,最好選擇0為作為默認值。 Planet 枚舉沒有任何明顯的默認值。當使用者沒有指定默認值,它不會隨意選擇一個枚舉值。如果你遇到這種情況,使用0作為未初始狀態然后在后面更新:
```
public enum Planet2
{
None = 0,
Mercury = 1,
Venus = 2,
Earth = 3,
Mars = 4,
Jupiter = 5,
Saturn = 6,
Neptune = 7,
Uranus = 8
}
Planet sphere = new Planet();
```
sphere 現在的值是空。給 Planet 枚舉加上未初始化的默認值會擴散到 ObservationData 結構體中。這樣新創建的 ObservationData 有一個0 magnitude 和空為目標。添加顯示構造函數讓使用者顯示初始化所有域的值:
```
public struct ObservationData
{
Planet whichPlanet; //what am I looking at?
double magnitude; // perceived brightness.
ObservationData(Planet target,
double mag)
{
whichPlanet = target;
magnitude = mag;
}
}
```
但是記住默認構造函數仍可見的還是構造函數的一部分。使用者還是可以創建系統初始化的變量,并且你不能阻止他們。
這仍然有些錯誤,因為沒有真的觀察是沒有任何意義。你可以通過改變 Observation 為類解決這個特例,這樣無參構造函數就不可用了。但是,如果你創建枚舉,你不可能強制其他開發者遵從這個規則。最好的最好是創建的枚舉類型的0位模式是有效的,即使這不是一個完美的抽象。
在討論其他類型之前,你需要理解使用 enum 作為標記的一些特殊例子。 enum 使用標記特性需要總是設置 None 值為0。
```
[Flags]
public enum Styles
{
None = 0,
Flat = 1,
Sunken = 2,
Raised = 4,
}
```
很多開發者對標記位枚舉值使用按位運算 AND 操作符。0值在位標記會引起嚴重問題。下面的測試當 Flat 是0值時不會工作:
```
if ((flag & Styles.Flat) != 0) // Never true if Flat == 0\.
DoFlatThings();
```
如果你使用標記位,確保0是有效的,它表示“沒有任何標記”。
另一個常見初始化問題涉及到值類型包含引用類型。string 是常見的例子:
```
public struct LogMessage
{
private int ErrLevel;
private string msg;
}
LogMessage MyMessage = new LogMessage();
```
MyMessage 包含為 null 的 msg 域引用。沒有任何方式去強制不同的初始化,但是你可以使用屬性本地化這個問題。你可以創建一個屬性想所有使用者暴露 msg 的值。添加邏輯使得屬性返回空字符串而不是 null:
```
public struct LogMessage2
{
private int ErrLevel;
private string msg;
public string Message
{
get
{
return (msg != null) ? msg : string.Empty;
}
set
{
msg = value;
}
}
}
```
你應該在你的類的內部使用這個屬性。總是只在一個地方檢查 null 引用。當你從你的程序集中調用 Mesaage 的訪問也是 inline 的。你會活得高效的代碼和最少的錯誤。
系統會默認初始化所有實例值為0。沒有任何方法阻止使用者創建值類型的實例不都是0。如果可能,使得所有0都是自然默認值。一個特殊情況是,enum 作為標記位使用應該確保0表示沒有任何標記位。
小結:
主要是 struct 和 enum 值類型的默認初始化,要能保證0值時一個有效的值。
歡迎各種不爽,各種噴,寫這個純屬個人愛好,秉持”分享“之德!
有關本書的其他章節翻譯請[點擊查看](/category/297763),轉載請注明出處,尊重原創!
如果您對D.S.Qiu有任何建議或意見可以在文章后面評論,或者發郵件(gd.s.qiu@gmail.com)交流,您的鼓勵和支持是我前進的動力,希望能有更多更好的分享。
轉載請在**文首**注明出處:[http://dsqiu.iteye.com/blog/2079730](/blog/2079730)
更多精彩請關注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<T> 和 IComparer<T> 實現排序關系
- 原則32:避免 ICloneable
- 原則33:只有基類更新處理才使用 new 修飾符
- 原則34:避免定義在基類的方法的重寫
- 原則35:理解 PLINQ 并行算法的實現
- 原則36:理解 I/O 受限制(Bound)操作 PLINQ 的使用
- 原則37:構造并行算法的異常考量
- 第五章 雜項討論
- 原則38:理解動態(Dynamic)的利與弊
- 原則39:使用動態對泛型類型參數的運行時類型的利用
- 原則40:使用動態接收匿名類型參數