随笔 - 89, 评论 - 163, 引用 - 33

导航

关于

标签

每月存档

最新留言

广告

 

[原文作者]: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>相当于陈述了“该对象明白相等的含义”,即在你的类中声明了对象之间如何比较是否相等,你的对象应该实现这个功能,防止使其他不熟悉这个对象的程序员迷惑,这可是件麻烦事。

    问题1IEqualityComparer<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:暂无标签

留言反馈

暂时没有留言纪录
博客主人设置本博客不允许匿名用户发表言论,请登录后再试

Powered by: Joycode.MVC引擎 0.5.2.0