More Effective C# 05.確保 0 是實值型別的有效狀態 More Effective C# 05.確保 0 是實值型別的有效狀態(Ensure That 0 Is a Valid State for Value Types)

這個做法在建議處理 Value Types 相關的型別時因為預設初始化為 0,所以要確保為 0 的時候型別要能正常運作或者是有效的狀態。

在 .net 中預設的初始化就是將物件設定為 0,所以只能夠把 0 當成你的型別中的有效預設值,像是在 DecompressionMethods 這個 enum 中 0 就代表 None,也能發現它是從 0 開始編號的。

void Main()
{
	Console.WriteLine(default(int));
	Console.WriteLine(default(HttpStatusCode));
}


[Flags]
public enum DecompressionMethods
{
	None = 0,
	GZip = 1,
	Deflate = 2,
	Brotli = 4,
}

例如在這個例子中,sphereanotherSphere 變數的值都是 0。

public enum Planet
{
   Mercury = 1,
   Venus = 2,
   Earth = 3,
   Mars = 4,
   Jupiter = 5,
   Saturn = 6,
   Uranus = 7,
   Neptune = 8
}
 
Planet sphere = new Planet();
var anotherSphere = default(Planet);

那麼使用 Planet enum 的 ObservationData 物件,就會產生不正確的狀態,對於 double 來說 0 是一個很合理得值, 但是對於 whichPlanet 來說 0 是沒有意義的預設值,這會導致 ObservationData 物件處在一個不正確的狀態, 語意上來說一筆 0 的行星且星等為 0 的觀察資料也不通順。

public struct ObservationData
{
   private Planet whichPlanet;
   private double magnitude;
}

ObservationData d = new ObservationData();

如果在 Planet enum 中新增預設值 None,這樣語意就變成一筆不存在行星且星等為 0 的觀察資料,這樣就明確了許多。

public enum Planet
{
   None = 0,
   Mercury = 1,
   Venus = 2,
   Earth = 3,
   Mars = 4,
   Jupiter = 5,
   Saturn = 6,
   Uranus = 7,
   Neptune = 8
}

另外要注意使用 FlagsAttribute 的時候一定要設定 None = 0,因為在進行 bitwise AND 運算的時候如果是 0 並且沒有設定 None 的場合, 會導致下面的 if 判斷永遠不會進入。

[Flags]
public enum Styles
{
	None = 0,
	Flat = 1,
	Sunken = 2,
	Raised = 4,
}

void Main()
{
	Styles flag = Styles.Sunken;
	if ((flag & Styles.Flat) != 0) // Never true if Flat == 0.
		Console.WriteLine(1);
}

還有一個常見的初始化問題,那就是一個內部包含Reference Types 的 Value Types,在初始化的時候 mag 欄位會為 null。

void Main()
{
	LogMessage MyMessage = new LogMessage();
}

public struct LogMessage
{
	private int ErrLevel;
	private string msg;
}

要處理這個問題可以建立一個屬性並添加邏輯將預設值設定為 string.Empty,這樣就可以避免 null 檢查散落到程式各處。

public struct LogMessage
{
	private int ErrLevel;
	private string msg;
	public string Message
	{
		get => msg ?? string.Empty;
		set => msg = value;
	}
}

Summary

系統會預設把所有 Value Types 的實例化設定為 0,因此必須避免 Enum 相關的程式碼在處理 0 值的問題時發生問題,所以要假設使用者傳入的參數 全部為 0 當成預設的情況,並要注意使用 FlagsAttribute 的時候一定要設定 0 用來代表沒有任何 flags 的意思。