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


151

我想了解的场景中IEqualityComparer<T>,并IEquatable<T>应使用。两者的MSDN文档看起来都非常相似。


1
MSDN服务: “此接口允许对集合执行自定义的相等比较 ”当然,这是在所选答案中得出的。MSDN还建议继承EqualityComparer<T>而不是实现接口,因为“因为EqualityComparer<T>使用IEquatable<T>
–radarbob

...以上建议我应该为任何T实现者创建一个自定义集合IEquatable<T>。像这样的集合中List<T>是否会有某种细微的错误?
—radbobob


@RamilShavaleev链接已断开
ChessMax

Answers:


117

IEqualityComparer<T>是一个对象的接口,该对象对两个类型的对象进行比较T

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


1
IComparable / IComparer之间存在相同的区别
boctulus'1

3
上尉队长
斯蒂芬·伊凡

(已编辑)IEqualityComparer<T>是对象的接口(通常是不同于的轻量级类T,它提供可在以下T
条件下

61

在决定是否使用IEquatable<T>或时IEqualityComparer<T>,人们可能会问:

是否有测试两种T相等性实例的首选方法,或者有几种同等有效的方法?

  • 如果只有一种方法可以测试两个实例的T相等性,或者首选几种方法中的一种,IEquatable<T>则将是正确的选择:该接口应该仅由其T自身实现,以便一个实例T具有内部知识。如何与另一个实例比较T

  • 另一方面,如果有几种相等的比较两个Ts的相等合理方法,则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>实现,因此如果它不能满足您的目的,则可以将其替换掉。


6
测试双点相等性的建议比较方法被打破了,因为任何合法的IEqualityComparer<T>实现都必须实现一个等效关系,这意味着在所有两个对象等于第三个对象的情况下,它们必须彼此相等。与上述方法相反IEquatable<T>IEqualityComparer<T>以相反方式实现的任何类都将被破坏。
2013年

5
一个更好的例子是字符串之间的比较。有一个字符串比较方法很有意义,该方法仅在字符串包含相同字节序列时才将它们视为相等,但是还有其他有用的比较方法也构成了等效关系,例如不区分大小写的比较。注意,一个IEqualityComparer<String>认为“ hello”等于“ Hello”和“ hElLo”的a必须认为“ Hello”和“ hElLo”彼此相等,但是对于大多数比较方法而言,这并不是问题。
2013年

@supercat,感谢您的宝贵反馈。在接下来的几天里,我可能不会修改我的答案,因此,如果您愿意,请根据需要随意编辑。
stakx-

这正是我需要的,我正在做的。但是,需要重写GetHashCode,一旦值不同,它将返回false。您如何处理GetHashCode?
teapeng

@teapeng:不确定您在说什么具体用例。一般来说,GetHashCode应该允许您快速确定两个值是否不同。规则大致如下:(1) GetHashCode必须始终为相同的值产生相同的哈希码。(2) GetHashCode应该快(比快Equals)。(3) GetHashCode不必精确(不如精确Equals)。这意味着它可能针对不同的值产生相同的哈希码。您可以做得越精确越好,但保持快速可能更重要。
stakx-

27

您已经了解了它们的基本定义。简而言之,如果您IEquatable<T>在class 上实现T,则Equals对类型对象的方法将T告诉您对象本身(正在测试相等性的对象)是否等于相同类型的另一个实例T。而IEqualityComparer<T>用于测试的任何两个实例的相等性T,通常超出的实例范围T

至于它们是什么,可在第一混淆。从定义中应该清楚,因此IEquatable<T>(在类T本身中定义)应该是代表其对象/实例唯一性的事实上的标准。HashSet<T>Dictionary<T, U>(考虑到GetHashCode被重写为好),ContainsList<T>等利用这个。实现IEqualityComparer<T>T上述一般情况没有帮助。随后,除了以外,IEquatable<T>在其他任何类别上实现的价值都很小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

同样,IEqualityComparer<T>打开T没有意义。

class Person : IEqualityComparer<Person>

的确,这是可行的,但看起来并不好看,并且会破坏逻辑。

通常您需要的是IEquatable<T>。同样理想的是,根据不同的标准,您只能有一个,IEquatable<T>而可能有多个IEqualityComparer<T>

IEqualityComparer<T>IEquatable<T>是完全类似Comparer<T>IComparable<T>其用于比较目的,而不是等同; 一个很好的线程这里我写了同样的答案:)


public int GetHashCode(Person obj)应该返回obj.GetHashCode()
hyankov '16

@HristoYankov应该返回obj.Age.GetHashCode。将进行编辑。
nawfal

请解释为什么比较器必须对比较对象的哈希值做出这样的假设?您的比较器不知道Person如何计算其哈希,为什么要覆盖它?
hyankov '16

@HristoYankov 您的比较器不知道Person如何计算其哈希 -当然,这就是为什么我们没有person.GetHashCode在任何地方直接调用.... 您为什么要覆盖它?-我们之所以会优先考虑,IEqualityComparer是因为我们的重点是根据我们的规则(我们非常了解的规则)使用不同的比较实现。
nawfal

在我的示例中,我需要基于Age属性进行比较,因此我致电Age.GetHashCode。Age是类型int,但是.NET中的哈希码协定是什么类型different objects can have equal hash but equal objects cant ever have different hashes。这是我们可以盲目相信的合同。当然,我们的自定义比较器也应遵守此规则。如果调用someObject.GetHashCode中断了事情,那么这是类型的实现者的问题someObject,这不是我们的问题。
nawfal

11

IEqualityComparer用于在外部实现两个对象的相等性时使用,例如,如果您想为没有来源的两种类型定义比较器,或者在只有有限的上下文中才有意义的两种情况下。

IEquatable是针对对象本身(被比较是否相等的对象)实现的。


您是唯一真正提出过“插拔平等”(外部使用)问题的人。加1.
罗伊·纳米尔

4

一比较二T。另一个可以将自己与其他Ts 进行比较。通常,您一次只需要使用一个,而不必一次使用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.