1、引言
首先我們先來看看IEquatable<T>接口的出現解決了什么問題。
我們知道,Object基類的Equals方法存在兩個明顯的問題。一是缺乏類型安全性,二是對於值類型而言需要裝箱。在本文中我們就來看下IEquatable<T> Interface是如何解決這兩個問題的。
2、IEquatable<T>接口
我們都知道的一個事實是:如果想讓Object的Equals方法為所有派生類型所用,那么,它的參數就必須設計成object類型。
object是引用類型,這就意味着,如果傳遞一個值類型的參數,那么該參數將被裝箱,這就會造成性能損失。
另外,還存在另一個問題:將object類型設為參數還意味着類型安全性的缺失。
解決裝箱和類型安全性問題的一個辦法就是定義一個新的Equals方法,該方法接受一個和待比較類型相同類型的參數。例如,對於字符串類型而言,定義一個接受string類型的Equals方法就能圓滿解決這兩個問題。
但這會面臨另一個新的問題,那就是:定義強類型的方法和OOP中的繼承存在根本的沖突。我們不能在Object基類中定義一個強類型的Equals方法,因為Object基類根本無法知曉派生類的類型。
那么,我們怎么樣才能定義一個強類型的Equals方法,同時該方法能被所有類型使用呢?微軟解決這個問題的思路就是通過提供一個IEquatable<T>接口,該接口向所有類型暴露。查看該接口的定義時,可以發現它僅暴露了一個Equals方法,如下所示。
using System; namespace System { public interface IEquatable<T> { bool Equals(T other); } }
該Equals方法和Object基類的虛Equals方法的作用相同,只不過它接受一個T類型參數,因此,它是強類型的,這意味着對於值類型而言,不存在裝箱的問題。
3、IEquatable<T>接口和值類型
我們可以通過一個簡單的例子來證明IEquatable<T>接口的使用。
static void Main(String[] args) { int number1 = 1; int number2 = 2; int number3 = 1; Console.WriteLine(number1.Equals(number2)); Console.WriteLine(number1.Equals(number3)); }
在上面的例子中,我們定義了三個整型變量,然后使用Equals方法進行比較。在VS中借助智能感知,可以發現對於int類型而言存在兩個Equals方法,一個接受object參數,另一個接受int類型參數。接受int參數的Equals方法實現了IEquatable<T>接口,其中,T為int類型。因為我們在調用Equals方法時傳遞的是一個int類型變量,而不是一個object變量,因此,編譯器將選擇實現了IEquatable<T>接口的Equals方法。
在平常開發中對於int類型的比較,我們不會像上面那樣使用Equals方法進行比較,而是使用更加簡便明了的==操作符。
所有的基元類型都提供了對IEquatable<T>接口的實現,就像上面代碼中的int類型那樣,int類型實現了IEquatable<int>。
總體而言,IEquatable<T>接口對值類型非常有用。但微軟並沒有為FCL中的非基元的值類型實現該接口,因此,不能寄希望於對FCL中值類型而言總是可以使用該接口。
4、IEquatable<T>和引用類型
對於引用類型而言,IEquatable<T>接口並沒有那么有用。一是因為引用類型不存在像值類型那樣的由裝箱導致的性能問題,二是因為IEquatable<T>接口不能很好地處理繼承問題。
但值的注意的是,String類型實現了IEquatable<T>接口,如下面所示
static void Main(String[] args) { string s1 = "Hello World"; string s2 = string.Copy(s1); Console.WriteLine(s1.Equals(s2); }
上面的代碼中,C#編譯器將直接選擇強類型的Equals方法。另外,String類型是sealed的,因此,你不能從它繼承。這樣,在相等性判定和繼承之間的沖突就不存在了。
很明顯,若一個類型定義了兩個Equals方法,我們希望它們對相同的輸入,產生相同的輸出。關於這一點,微軟提供的默認實現都嚴格履行了這一點。當我們自己去實現IEquatable<T>接口時,也要保證這一點。否則,別的開發者使用你定義的類型時將感到困惑。