[原文作者]:Jared Parsons
[原文链接]:If you implement IEquatable<T> you still must override Object’s Equals and GetHashCode
CLR2.0(公共语言运行时2.0)包含了一个接口IEquatable<T>,用来进行类型安全的比较操作。之前可用的最好方法是用虚方法Equals来比较,这个方法类型较松散,因为它将对象作为参数传入,当然,对于在类型适当要求不高的客户端来讲,这个方法足够了
class Student {
public override bool Equals(object obj) {
var other = obj as Student;
if (other == null) {
return false;
}
// rest of comparison
}
}
IEquatable<T>中一个非常显著的改进是它提供了强类型的比较方法,防止了调用者和被调用方传递无法比较的对象类型,当然也消除了值类型装箱的开销。
这些优点很吸引人,但是当你执行IEquatable<T>接口时,必须重载Equals(Object参数)和GetHashCode方法。否则你会付出些代价,在之前的文章中我简短的介绍了下这方面的内容,但是今天就几个例子我想深入讨论下这个话题。
在谈论技术细节之前,我们来想象一个场景,执行IEquatable<T>相当于陈述了“该对象明白相等的含义”,即在你的类中声明了对象之间如何比较是否相等,你的对象应该实现这个功能,防止使其他不熟悉这个对象的程序员迷惑,这可是件麻烦事。
问题1:IEqualityComparer<T>依赖于GetHashCode()
强类型的集合,例如Dictionary<TKey,TValue> 和HashSet<T>,必须比较对象是否相等来实现其功能,从BCL(Base class library)2.0开始,由接口IEqualityComparer<T>来比较对象之间语义是否相等。这个接口在集合之外的很多地方得到运用,但是集合相关的类最能够体现该接口的意义。
下面是IEqualityComparer<T>的定义
public interface IEqualityComparer<T> {
bool Equals(T x, T y);
int GetHashCode(T obj) ;
}
默认的定义来自BCL中的GenericEqualityComparer<T>类,IEqualityComparer<T>的默认实现依赖于IEquatable<T>的实现。
但是要实现IEquatable<T>的话要怎么实现GetHashCode()呢? 很简单,使用Object.GetHashCode()。这意味着一个对象想正常运行的话必须在IEqualityComparer<T>调用的地方实现IEquatable<T> 和GetHashCode()。
但是等一下,我没有明显的实现IEqualityComparer<T>所以我可以摆脱那些麻烦了吗?不是这样,很少有人真正实现IEqualityComparer<T>,而是用EqualityComparere<T>来替代,这个函数用来默认读取一个给定类型的IEqualityComparer<T>接口。
public static class Example {
public static IEnumerable<T> Distinct<T>(this IEnumerable<T> source) {
return Distinct(source, EqualityComparer<T>.Default);
}
public static IEnumerable<T> Distinct<T>(
this IEnumerable<T> source,
IEqualityComparer<T> comparer) {
// implementation
}
}
事实上,采用IEqualityComparer<T>接口的方法有个标准模式,就是EqualityComparer<T>采用重载而不是直接调用,这个模式默认如下
public static class Example {
public static IEnumerable<T> Distinct<T>(this IEnumerable<T> source) {
return Distinct(source, EqualityComparer<T>.Default);
}
public static IEnumerable<T> Distinct<T>(
this IEnumerable<T> source,
IEqualityComparer<T> comparer) {
// implementation
}
}
如果你的对象实现了IEquatable<T>,那么会创建一个GenericEqualityComparer<T>的实例,对象也因此依赖于GetHashCode方法(比较时要求用到)。
问题2:弱类型集合及框架不使用IEquatable<T>
IEquatable<T>接口仅提供强类型的相等比较,弱类型访问接口时相等比较始终是棘手的问题。考虑实例化最初1.0版本的集合类:ArrayList, Hashtable,等等,这些基于对象的集合都无法使用IEquatable<T>接口。因此这些集合必须依赖于比较内部对象的方法。
如果不实现Object.Equals和Object.GetHashCode方法,你的类型可能无法进行任何比较操作。这样会导致类中对象相等等操作的不正确结果。
class Person : IEquatable<Person> {
public readonly string Name;
public Person(string name) {
Name = name;
}
public bool Equals(Person other) {
if (other == null) {
return false;
}
return StringComparer.Ordinal.Equals(Name, other.Name);
}
}
static void EqualityCheck() {
var p = new Person("Bob");
var list = new ArrayList();
list.Add(p);
Console.WriteLine(list.Contains(p)); // Prints: True
Console.WriteLine(list.Contains(new Person("Bob"))); // Prints: False
}
这和我们想要的相去甚远。这个例子中,两个Person的实例定义相同,但是在Contains方法中会产生错误。实现Object.Equals 和Object.GetHashCode两个方法会移除这个问题。
以下框架仍然是用松散类型集合:WinForms, WPF, WebForms,等等,因此你肯定会在工程中遇到需要松散类型的情况。
问题3:判断对象是否相等与哈希码在BCL中是密不可分的
无论如何,判断对象是否相等与哈希码在BCL中是密不可分的,如果一个对象可以用来比较那么它一定可以生成一个哈希码。这种隐秘的联系存在于框架内部的许多地方。
如上文所述,实现IEquatable<T>接口之后填充Object.Equals()是铁板钉钉的事实,Object.GetHashCode可能有点麻烦因为GetHashCode有许多隐秘的内部联系。频繁变化的对象很难提供一个有效的哈希机制,这种情况下就返回1。这样所有关于GetHashCode()的内部联系都可以运行,且耗时很少。也许这种方法会让Dictionary更像一个链表,但是总比不能运行好吧。
打印 | 张贴于 2009-02-20 14:11:29 | Tag:暂无标签
留言反馈