自定义实现两个对象的相等比较,一种方案是重写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(); } }