【问题标题】:Why implement IEqualityComparer<T> in a separate class为什么在单独的类中实现 IEqualityComparer<T>
【发布时间】:2016-02-10 11:51:22
【问题描述】:

当我在msdn 上查找通用IEqualityComparer 接口时,我注意到该接口是在一个单独的“比较器”类中实现的,而不是在类本身中实现的IEquatable&lt;T&gt;。当我搜索更多示例时,每个示例都使用一个单独的类,这让我想知道:为什么不在类本身上实现它?

我可以想象,覆盖 object.Equalsobject.GetHashCode 并不被认为是好的做法,因为它用于许多不同的情况,但即使是 msdn 也说(强调我的):

此接口允许为集合实现自定义相等比较。

所以它的用途几乎仅限于 Linq。我能想到为什么要定义一个单独的比较器类只有两个原因:

  1. 类集合上的不同方法需要不同的比较器。
  2. 该类很大,不希望实例化它的另一个对象(尽管如果这确实是问题所在,为什么不拥有它的完整集合还不错?)。

所以我的问题是:

是否有任何我忽略的特殊原因导致每个人都定义另一个比较器类只是为了进行比较,而不是仅仅在类本身上实现接口(至少在我看来这不会更糟)?

一个小例子:

public static void Main(string[] args)
{
    Test t1 = new Test { id = 1, date = default(DateTime) };
    Test t2 = new Test { id = 1, date = default(DateTime) };
    Test t3 = new Test { id = 0, date = default(DateTime) };
    List<Test> testList = new List<Test>{ t1, t2, t3 };

    //Same result
    int distinctCountClass = testList.Distinct(new Test()).Count();
    int distinctCountComparerClass = testList.Distinct(new TestComparer()).Count();
}

public partial class Test
{
    public int id { get; set; }
    public DateTime date { get; set; }
}

public partial class Test : IEqualityComparer<Test>
{
    public bool Equals(Test x, Test y) { return x.id == y.id && x.date == y.date; }
    public int GetHashCode(Test obj) { return obj.id.GetHashCode(); }
}

public class TestComparer : IEqualityComparer<Test>
{
    public bool Equals(Test x, Test y) { return x.id == y.id && x.date == y.date; }
    public int GetHashCode(Test obj) { return obj.id.GetHashCode(); }
}

【问题讨论】:

  • 第一个原因你是正确的,第二个原因是部分正确的。这个类是“大”的,但它与实例化无关,而是与“你的类做的太多”有关。代表一组特定数据或行为的类与比较两个这样做的类的类有很大不同。
  • 使用一个对象的实例来比较两个相同类型的其他实例似乎很不自然......如果你想为该类型中的一个类型实现一个比较运算符,你应该实现@ 987654327@
  • @MatthewWatson 是的,但是许多 linq 方法需要 IEqualityComparer&lt;T&gt;、Distinct、Groupby 等实例。如果我只实现 IEquatable&lt;T&gt; 这些方法将不起作用
  • 如果你想使用IEqualityComparer&lt;T&gt; 类型T 然后实现IEquatable&lt;T&gt; 并使用EqualityComparer&lt;T&gt;.Default 它将自动使用你为@ 编写的IEqualityComparer&lt;T&gt; 的实现987654335@.
  • 有点跑题了,但我也想指出,在实现IEqualityComparer&lt;T&gt; OR IEquatable&lt;T&gt; 时,通常应该只在比较中使用不可变属性。原因是,如果它们是可变的,并且您将对象放入 HashSet 之类的集合中,然后改变其中一个属性,则可能会发生令人讨厌的事情。

标签: c# linq compare


【解决方案1】:

为什么不在类本身实现呢?

因为它没有意义。 IEqualityComparer&lt;T&gt; 的全部目的是在 T 类型之外实现,因为它针对您帖子中的 "原因 1"

如果您希望类本身实现相等逻辑,那么您应该实现专门为这种情况提供的IEquatable&lt;T&gt;,并且EqualityComparer&lt;T&gt;.Default 将在需要IEqualityComparer&lt;T&gt; 的任何时候为您的实现提供必要的桥梁并且没有明确指定。

由于该类只能提供一个硬编码逻辑而没有任何动态行为和/或选项,因此它被认为是 默认 相等逻辑,因此静态 EqualityProvider&lt;T&gt; 属性的名称提供对它。

【讨论】:

  • 我没有意识到 EqualityComparer&lt;T&gt;.Default 使用了 IEquatable&lt;T&gt;,正如 Matthew 在 cmets 中指出的那样,这对我来说确实是最好的解决方案 :)
  • 嗯,我还没有读过他的 cmets,但现在我读过了,所以看起来我只是在确认它:)
  • 接受这是我将要使用的方法
【解决方案2】:

IComparer&lt;T&gt;IEqualityComparer&lt;T&gt; 与 T 的 两个 实例一起工作,因此它们不需要作为 T 类的一部分来实现;但是,在T 中实现IEqualityComparer&lt;T&gt; 是一种很好的做法,该方案可以是

  public partial class Test {
    private class TestComparer : IEqualityComparer<Test> {
      public bool Equals(Test x, Test y) { 
        return x.id == y.id && x.date == y.date; 
      }

      public int GetHashCode(Test obj) { 
        return obj.id.GetHashCode(); 
      }
    }

    // Please, note "static"
    public static IEqualityComparer<Test> MyTestComparer {get;} = new TestComparer();

    public int id { get; set; }
    public DateTime date { get; set; }
    ...
  }

在这种情况下,您只需使用所需的比较器:

int distinctCountComparerClass = testList.Distinct(Test.MyTestComparer).Count();

【讨论】:

  • 没想到在课堂上定义它,我喜欢这样。我不喜欢单独的类,因为我不喜欢创建大量贡献不大的帮助类
  • 在我看来,您应该为该类实现IEquatable&lt;T&gt;,如果消费者想要该类的比较器,则让他们使用EqualityComparer&lt;T&gt;.Default
  • @Alexander Derck:当类 (Test) 比TestComparerTest 中的其他比较器足够简单时,看起来还可以;当Test 很复杂时,将所有比较器组合成特殊的public static class TestComparer 是一种更好的方法(参见StringStringComparer 类)
【解决方案3】:

简单地说,通过这种方式,您可以根据上下文使用不同的方法来比较同一类中的对象。

这基本上是控制反转:不是由类本身来决定另一个类可能想要如何比较它的实例。

【讨论】:

    【解决方案4】:

    实现IEqualityComparer&lt;T&gt; 而不是IEquatable&lt;T&gt; 是一个好习惯,因为当一个类实现IEquatable&lt;T&gt; 接口时,它会进入一个约定,其中声明“我知道如何比较@987654324 类型的两个实例@ 或从 T 派生的任何类型以表示相等”。但是,如果该类是派生的,则基类不太可能知道如何进行有意义的比较。因此,这个隐含的契约现在被打破了。

    或者,我们希望其实例为comparable 的类可以设置为sealed,但由于此对话线程中已经说明的原因,另一种方法要优雅得多。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-08
      • 1970-01-01
      • 2012-10-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多