IEqualityComparer<T>和IEquatable<T>有什么区别?

183

1
“MSDN说:此接口允许为集合实现自定义的相等比较。当然,这在所选答案中已经暗示了。MSDN还建议继承EqualityComparer<T>而不是实现接口,因为EqualityComparer<T>使用IEquatable<T>测试相等性。” - radarbob
以上内容表明,我应该为任何实现IEquatable<T>T创建自定义集合。否则,像List<T>这样的集合会有某种微妙的错误吗? - radarbob
良好的解释 http://www.anotherchris.net/csharp/icomparable-icomparer-equals-iequatablet-iequalitycomparer/ - Ramil Shavaleev
2
@RamilShavaleev,链接已经失效。 - ChessMax
5个回答

142

IEqualityComparer<T>是一个接口,用于比较两个类型为T的对象。

IEquatable<T>是用于类型为T的对象,以便它可以将自己与另一个相同类型的对象进行比较。


5
IComparable和IComparer之间的区别是相同的。 - boctulus
2
IEqualityComparer<T> 是一个接口,用于提供在 T 上操作的比较函数的对象(通常是与 T 不同的轻量级类)。 - rwong
Enumerable.Except使用哪一个? - John Demetriou

77

当决定使用 IEquatable<T> 还是 IEqualityComparer<T> 时,你可以问:

是否有更倾向于测试两个 T 实例是否相等的方法,还是有几种同样有效的方法?

  • 如果只有一种方法可以测试两个T实例的相等性,或者有多种方法但是偏好其中一种,那么IEquatable<T> 就是正确的选择:这个接口应该只由T本身来实现,以便一个T实例有内部知识来比较自己和另一个T实例。

  • 另一方面,如果有几种同样合理的方法来比较两个T实例是否相等,IEqualityComparer<T> 似乎更为适当:这个接口不是由T本身来实现,而是由其他“外部”类来实现。因此,在测试两个T实例的相等性时,因为T没有内部了解相等性的知识,你必须明确选择一个IEqualityComparer<T> 实例,该实例根据你的特定要求执行测试。

示例:

让我们考虑这两种类型(应该具有值语义):

interface IIntPoint : IEquatable<IIntPoint>
{
    int X { get; }
    int Y { get; }
}

interface IDoublePoint  // does not inherit IEquatable<IDoublePoint>; see below.
{
    double X { get; }
    double Y { get; }
}

为什么只有其中一种类型继承了 IEquatable<>,而另一种没有?

理论上,比较这两种类型的实例只有一种明智的方式:如果两个实例中的 XY 属性相等,则它们相等。根据这种思考方式,这两种类型都应该实现 IEquatable<>,因为似乎没有其他有意义的进行相等性测试的方法。

问题在于,比较浮点数的相等性可能由于微小的舍入误差而无法按预期工作。有不同的比较浮点数的方法,每种方法都有特定的优缺点,您可能希望能够自己选择适当的方法。

sealed class DoublePointNearEqualityComparerByTolerance : IEqualityComparer<IDoublePoint>
{
    public DoublePointNearEqualityComparerByTolerance(double tolerance) { … }
    …
    public bool Equals(IDoublePoint a, IDoublePoint b)
    {
        return Math.Abs(a.X - b.X) <= tolerance  &&  Math.Abs(a.Y - b.Y) <= tolerance;
    }
    …
}

请注意,我链接的页面明确指出,这个近似相等性测试存在一些弱点。由于这是一个 IEqualityComparer<T> 的实现,如果不足以满足您的需求,您可以简单地将其替换掉。

7
测试双精度浮点数相等性的建议比较方法存在问题,因为任何合法的 IEqualityComparer<T> 实现都 必须 实现等价关系,这意味着在所有情况下,如果两个对象与第三个对象相等,则它们 必须 相互相等。任何以与上述规则相反的方式实现 IEquatable<T>IEqualityComparer<T> 的类都是有问题的。 - supercat
6
更好的例子是字符串之间的比较。具有将字符串视为仅在它们包含相同字节序列时相等的字符串比较方法是有意义的,但还有其他有用的比较方法(也构成等价关系),如不区分大小写的比较。请注意,一个IEqualityComparer<String>认为"hello"等于"Hello"和"hElLo"必须同时认为"Hello"和"hElLo"相互相等,但对于大多数比较方法而言,这不会成为问题。 - supercat
@supercat,感谢您宝贵的反馈。由于接下来几天可能无法进行修订,如果您愿意,可以随意编辑。 - stakx - no longer contributing
这正是我所需要的,也是我正在做的。但是有必要重写 GetHashCode 方法,在其中一旦值不同就返回 false。你是如何处理你的 GetHashCode 的呢? - TPG
@teapeng:不确定你所说的具体用例是什么。一般来说,GetHashCode 应该允许你快速确定两个值是否不同。大致规则如下:**(1)** 对于相同的值,GetHashCode 必须始终生成相同的哈希码。**(2)** GetHashCode 应该很快(比 Equals 更快)。**(3)** GetHashCode 不必精确(不像 Equals 那样精确)。这意味着它可能为不同的值产生相同的哈希码。您能使其更精确就越好,但保持其快速可能更重要。 - stakx - no longer contributing

39
您已经了解了它们是什么的基本定义。简而言之,如果您在类T上实现IEquatable<T>,则类型为T的对象的Equals方法将告诉您该对象本身(正在测试相等性的对象)是否等于同一类型T的另一个实例。而IEqualityComparer<T>用于测试任意两个T实例的相等性,通常超出了T实例的范围。
至于它们的用途可能会令人困惑。从定义中应清楚地看出,因此IEquatable<T>(在类T本身中定义)应成为表示其对象/实例唯一性的事实标准。HashSet<T>Dictionary<T, U>(还考虑到GetHashCode被覆盖),List<T>上的Contains等都使用了这个。在T上实现IEqualityComparer<T>对于上述常见情况没有帮助。随后,在除T以外的任何其他类上实现IEquatable<T>的价值很小。
class MyClass : IEquatable<T>

很少有意义。
另一方面,
class T : IEquatable<T>
{
    //override ==, !=, GetHashCode and non generic Equals as well

    public bool Equals(T other)
    {
        //....
    }
}

这是正确的做法。

IEqualityComparer<T> 可以在需要自定义相等性验证时非常有用,但并不是通行的规则。例如,在一个Person类中,您可能需要根据他们的年龄测试两个人的相等性。在这种情况下,您可以这样做:

class Person
{
    public int Age;
}

class AgeEqualityTester : IEqualityComparer<Person>
{
    public bool Equals(Person x, Person y)
    {
        return x.Age == y.Age;
    }

    public int GetHashCode(Person obj)
    {
        return obj.Age.GetHashCode;
    }
}

为了测试它们,尝试:
var people = new Person[] { new Person { age = 23 } };
Person p = new Person() { age = 23 };

print people.Contains(p); //false;
print people.Contains(p, new AgeEqualityTester()); //true

同样地,在T上使用IEqualityComparer<T>是没有意义的。
class Person : IEqualityComparer<Person>

确实这可以运行,但看起来不好看,并且违反了逻辑。

通常你需要的是IEquatable<T>。理想情况下,你只能拥有一个IEquatable<T>,而基于不同标准可能会有多个IEqualityComparer<T>

IEqualityComparer<T>IEquatable<T>与用于比较而非等价的Comparer<T>IComparable<T>完全类似;我在这里写了同样的答案 :)


public int GetHashCode(Person obj) should return obj.GetHashCode() - hyankov
@HristoYankov 应该返回 obj.Age.GetHashCode。将进行编辑。 - nawfal
请解释一下为什么比较器必须对它所比较的对象的哈希做出这样的假设?您的比较器不知道Person如何计算其哈希值,为什么要覆盖它呢? - hyankov
@HristoYankov 你的比较器不知道Person如何计算其哈希值 - 当然,这就是为什么我们没有直接在任何地方调用person.GetHashCode.... 为什么要重写它? - 我们重写它是因为IEqualityComparer的整个意义在于根据我们真正熟悉的规则拥有不同的比较实现 - 规则。 - nawfal
在我的例子中,我需要基于Age属性进行比较,因此我调用Age.GetHashCode。Age的类型是int,但无论类型如何,在.NET中哈希码合同是“不同的对象可以具有相等的哈希码,但相等的对象永远不能具有不同的哈希码”。这是一个合同,我们可以盲目地相信。当然,我们自定义的比较器也应遵守这个规则。如果调用someObject.GetHashCode会出问题,那么问题在于someObject的类型实现者,而不是我们的问题。 - nawfal
这个答案太棒了,伙计。我在任何地方都找不到一个易懂的解释。谢谢! - Michael Haddad

14

IEqualityComparer用于在两个对象的相等性是外部实现时使用,例如,如果您想要为两种类型定义一个比较器,但您没有源代码,或者在两个东西之间的相等性只有在某些有限的情况下才有意义。

IEquatable用于对象本身(被比较相等性的对象)进行实现。


你是唯一一个实际提出“插入等式”(外部使用)问题的人。再加1。 - Royi Namir

5

一个用于比较两个T的对象。另一个可以将自己与其他T进行比较。通常,您只需要一次使用一个对象,而不是两个对象。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接