談談C#的相等性(2)
該用什麼寫法?
很自然就會產生疑問,既然有三種相等性的寫法,平常寫程式時,該用什麼方法比較好?operator == 比較的是value還是reference ? 而且和.Equals差別在哪裡?
首先必須要理解 operator == 的行為。
operator == 的行為
operator == 左右運算元的型別應該要是對稱的。
一言以蔽之,左右是Value Type ==> 比Value Equality,是Reference Type ==> 比Reference Equality
- built-in value type,則比較value equality(反正也只能比這個) built-in value type是什麼? 給個比較不精確的定義,就是那些內部其實是數字的型別。像是bytes, short, int, long. 其他還有char(其實他是ascii code)、enum(內部是整數)等。浮點數當 然也是built-in value type。**注意字串不算喔。**唯一的例外是NaN,這個值不管跟什麼比都是false。
- 對 user-defined 的 value type 物件,也就是 struct ,== 並沒有定義其行為。編譯會不給過。開發者需要提供運算子多載。
- 對於reference type的處理
- null == null
- 如果是string,比較其長度和內容
- string以外的reference type,比較reference equality。底層參考到相同物件才視為相等。內容不管。
string
注意到string是個 reference type 的特例,其實他也沒有那麼特別。string 本身是個reference type。但是因為太常被使用了,所以C#特別設計了string intern機制。所有字面宣告的字串常數會被放到一個string pool內,相同內容的string會參考同一個底層string物件。因為是同一個底層物件,所以會參考比較會相等。
string a = "zebra";
string b = "zebra";
a == b // true
ReferenceEqual(a, b); // true
換個寫法就可以看出差異,兩個是不同的物件,所以ReferenceEqual是false。但是 operator == 卻是true。
string a = "zebra";
string b = "ze";
string c = "bra";
a == b + c // true
ReferenceEqual(a, b + c); // false
因為C#已經偷偷多載了operator == 的行為,改為比較內容是否相等。
public static bool operator == (string a, string b);
Reference Type 比較
== 對於 左右運算元是value-type還是reference type有不同的比較方式。對class而言,既然 == 的預設行為是reference equality,如果我需要相等性比較,怎麼樣才是比較好的實踐?
答案是,有一個大略的原則,但實務上還是見仁見智。
舉例而言,要用==還是.Equals比較string的內容呢?其實各有優缺點。== 簡單方便直觀,但兩邊一定要是字串,而.Equals允許你用更明確地傳入Comparer,決定比較的方式。微軟在2004年的blog寫單純的字串比較總是使用==,2018年的Guide建議用.Equals。
大體上Reference Type的比較還是有一個原則的。根據2004年微軟C#在其blog的FAQ回應,建議是
- 對於class這類的reference type,如果想要比較其內容相等性,使用.Equals()方法。
- 對於value type,使用 == 是比較一致的作法。盡量多載 operator == ,而且要使其行為和obj.Equals(other)相同。如果不同的話這會是個不好的實踐。
class type依然有可能overload operator ==,把預設行為改成比較內容相等。因此如果想要比較兩個reference type是否參考到同一物件,使用靜態方法Object.ReferenceEquals(objA, objB)是最安全的。
public static bool ReferenceEquals (object objA, object objB);
根據微軟的Programming Guideline,如果class type需要比較Value Equality,而且有覆寫了.Equals(),那麼「你可以考慮」多載operator ==,使其行為保持一致。而且也非常建議你一起覆寫掉GetHashCode(),使內容相同的物件有相同的hash值。
這個「考慮」其實很有意思。因為有兩派意見,一派是Reference Type不應該多載 operator ==。MSDN上甚至還有專門的Design Warning(CA1046)。但是微軟自己的GuideLines又說看情況。
Most reference types must not overload the equality operator, even if they override Equals. However, if you are implementing a reference type that is intended to have value semantics, such as a complex number type, you must override the equality operator.
所以結論其實是沒有結論,端看你定義的reference type是否有包含足夠的Value Semantic。