Skip to main content

談談C#的相等性(2)

該用什麼寫法?

很自然就會產生疑問,既然有三種相等性的寫法,平常寫程式時,該用什麼方法比較好?operator == 比較的是value還是reference ? 而且和.Equals差別在哪裡?

首先必須要理解 operator == 的行為。

operator == 的行為

operator == 左右運算元的型別應該要是對稱的。

一言以蔽之,左右是Value Type ==> 比Value Equality,是Reference Type ==> 比Reference Equality

  1. built-in value type,則比較value equality(反正也只能比這個) built-in value type是什麼? 給個比較不精確的定義,就是那些內部其實是數字的型別。像是bytes, short, int, long. 其他還有char(其實他是ascii code)、enum(內部是整數)等。浮點數當然也是built-in value type。**注意字串不算喔。**唯一的例外是NaN,這個值不管跟什麼比都是false。
  2. 對 user-defined 的 value type 物件,也就是 struct ,== 並沒有定義其行為。編譯會不給過。開發者需要提供運算子多載。
  3. 對於reference type的處理
    1. null == null
    2. 如果是string,比較其長度和內容
    3. 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。