自定義實現兩個對象的相等比較,一種方案是重寫Object類的Equals方法,很easy,如果相等返回true,不相等就返回false。不過,如果把自定義相等的比較用於泛型集,比如Dictionary、HashSet等,這些集合都有一個共同點——必須標識存儲項的唯一性,即每一個子項都有對應的key。
object.Equals方法是面向Object類型的,如果用於泛型對象,在判斷是否相等的過程需要進行大量的裝箱/拆箱操作,尤其是復合類型,由於要進行細致的比較,類型轉換更為頻繁,這樣會帶來一定量的性能開銷,所以,對於泛型集合的相等比較,應該考慮使用 IEqualityComparer<T>,Dotnet類型提供了一個實現了該接口的抽象類——EqualityComparer<T>。
在實際使用中,不妨直接實現這個抽象類,好處是該抽象類公開了一個靜態的Default屬性,可以返回平台默認的比較方案。因此,實現該抽象類的好處在於,既可以提供自定義實現,同時也可以保留默認行為。
我們先來解釋一下,為什么在泛型集合中需要用到自定義相等比較。看例子,咱們以常用的Dictionary為例,字典的Key我用一個叫Entity的類來標識,該類定義如下。
public class Entity { public int ID { get; set; } public string Name { get; set; } }
然后,我們實例化一個字典,並向其中添加兩個項。
IDictionary<Entity, string> dic = new Dictionary<Entity, string>(); dic.Add(new Entity { ID = 1, Name = "小明" }, "C++"); dic.Add(new Entity { ID = 2, Name = "小王" }, "VB");
接着,從字典中讀出Key為ID = 2 , Name = "小王" 的值。
Entity findkey = new Entity { ID = 2, Name = "小王" }; if (dic.ContainsKey(findkey)) { Console.WriteLine(dic[findkey]); }
在查找時,先實例化一個Entity,然后給ID和Name屬性賦上要查找的值,隨后調用字典實例的ContainsKey方法判斷一下要查找的key是否存在於字典中,如果存在,就輸出該key對應的值。
代碼看起來很完美,但一旦運行,你會發現什么都沒找到。為啥呢?
因為該字典存儲項的key是我們自定義的Entity類,當我們要從中查找時,是另外實例化了一個Entity對象,並賦了對應的屬性值去查找的,可是問題就來了,我們實例化的findkey對象,與存入到字典中的Entity不是同一個對象,雖然它們的屬性值相等,但它們引用的不是同一個實例,因為被判定為不相等的對象,故找不到對應的Key。
這個時候,EqualityComparer就派上用場了,自定義一個類並從它派生,添加自己的代碼實現,不管是不是同一個對象實例,只要屬性的值相等,則視為相同的key。
public sealed class CustomEqComparer : EqualityComparer<Entity> { public override bool Equals(Entity x, Entity y) { if (x.ID == y.ID && x.Name == y.Name) return true; return false; } public override int GetHashCode(Entity obj) { return obj.ID.GetHashCode(); } }
實現Equals方法,如果兩個對象相等,返回真,否則返回假。GetHashCode方法返回哈希值,算法不應該過於復雜,避免性能開銷,只要能夠保證相等的兩個對象返回相同的哈希值;不相等的對象返回不同的哈希值,這樣就可以了。這里直接以ID屬性的值為哈希,所以,每個key的ID值不能重復。
自定義完比較器后,只要在實例化字典實例時傳給它的構造函數就可以了。把上面的代碼改為:
CustomEqComparer comp = new CustomEqComparer(); IDictionary<Entity, string> dic = new Dictionary<Entity, string>(comp);
現在,再次執行前面的例子,ID=2,Name="小王"的key就可以查找出來了。
完整的演示代碼如下:
class Program { static void Main(string[] args) { CustomEqComparer comp = new CustomEqComparer(); IDictionary<Entity, string> dic = new Dictionary<Entity, string>(comp); dic.Add(new Entity { ID = 1, Name = "小明" }, "C++"); dic.Add(new Entity { ID = 2, Name = "小王" }, "VB"); Entity findkey = new Entity { ID = 2, Name = "小王" }; if (dic.ContainsKey(findkey)) { Console.WriteLine(dic[findkey]); } Console.Read(); } } public class Entity { public int ID { get; set; } public string Name { get; set; } } public sealed class CustomEqComparer : EqualityComparer<Entity> { public override bool Equals(Entity x, Entity y) { if (x.ID == y.ID && x.Name == y.Name) return true; return false; } public override int GetHashCode(Entity obj) { return obj.ID.GetHashCode(); } }