如何在不可变的泛型 Pair 结构上实现 IEqualityComparer?

Posted

技术标签:

【中文标题】如何在不可变的泛型 Pair 结构上实现 IEqualityComparer?【英文标题】:How do I implement IEqualityComparer on an immutable generic Pair struct? 【发布时间】:2010-09-12 08:33:21 【问题描述】:

目前我有这个(阅读建议后编辑):

struct Pair<T, K> : IEqualityComparer<Pair<T, K>>

    readonly private T _first;
    readonly private K _second;

    public Pair(T first, K second)
    
        _first = first;
        _second = second;

    

    public T First  get  return _first;  
    public K Second  get  return _second;  

    #region IEqualityComparer<Pair<T,K>> Members

    public bool Equals(Pair<T, K> x, Pair<T, K> y)
    
        return x.GetHashCode(x) == y.GetHashCode(y);
    

    public int GetHashCode(Pair<T, K> obj)
    
        int hashCode = obj.First == null ? 0 : obj._first.GetHashCode();

        hashCode ^= obj.Second == null ? 0 : obj._second.GetHashCode();

        return hashCode;
    

    #endregion

    public override int GetHashCode()
    
        return this.GetHashCode(this);
    

    public override bool Equals(object obj)
    
        return (obj != null) && 
    (obj is Pair<T, K>) && 
    this.Equals(this, (Pair<T, K>) obj);
    

问题是 First 和 Second 可能不是引用类型(VS 实际上警告我这一点),但代码仍然可以编译。我应该在比较它们之前将它们(第一和第二)转换为对象,还是有更好的方法来做到这一点?

编辑: 请注意,我希望此结构支持值和引用类型(换句话说,按类约束不是有效的解决方案)

编辑 2: 至于我想要实现的目标,我希望它可以在字典中工作。其次,SRP 现在对我来说并不重要,因为这并不是这个问题的真正本质——它总是可以在以后重构。第三,与 default(T) 比较不能代替与 null 比较 - 试试吧。

【问题讨论】:

【参考方案1】:

您的 IEqualityComparer 实现应该是一个不同的类(并且绝对不是一个结构,因为您想重用引用)。

此外,您的哈希码永远不应被缓存,因为结构的默认 GetHashcode 实现(您不会覆盖)将考虑该成员。

【讨论】:

【参考方案2】:

看来您需要 IEquatable:

internal struct Pair<T, K> : IEquatable<Pair<T, K>>

  private readonly T _first;
  private readonly K _second;

  public Pair(T first, K second)
  
    _first = first;
    _second = second;
  

  public T First
  
    get  return _first; 
  

  public K Second
  
    get  return _second; 
  

  public bool Equals(Pair<T, K> obj)
  
    return Equals(obj._first, _first) && Equals(obj._second, _second);
  

  public override bool Equals(object obj)
  
    return obj is Pair<T, K> && Equals((Pair<T, K>) obj);
  

  public override int GetHashCode()
  
    unchecked
    
      return (_first != null ? _first.GetHashCode() * 397 : 0) ^ (_second != null ? _second.GetHashCode() : 0);
    
  

【讨论】:

【参考方案3】:

如果您在比较方法中使用哈希码,如果哈希码相同,则应检查“真实值”。

bool result = ( x._hashCode == y._hashCode );
if ( result )  result = ( x._first == y._first && x._second == y._second ); 
// OR?: if ( result )  result = object.Equals( x._first, y._first ) && object.Equals( x._second, y._second ); 
// OR?: if ( result )  result = object.ReferenceEquals( x._first, y._first ) && object.Equals( x._second, y._second ); 
return result;

但是比较“_first”和“_second”字段有一点问题。 默认情况下,引用类型使用前相等比较“object.ReferenceEquals”方法,他们可以覆盖它们。因此,正确的解决方案取决于您的比较方法“究竟应该做什么”。应该使用“_first”和“_second”字段的“Equals”方法,还是 object.ReferenceEquals ?或者更复杂的东西?

【讨论】:

【参考方案4】:

关于警告,您可以使用 default(T) 和 default(K) 代替 null。

我看不出您想要实现什么,但您不应该使用哈希码来比较相等性 - 不能保证两个不同的对象不会具有相同的哈希码。此外,即使您的结构是不可变的,成员 _first 和 _second 也不是。

【讨论】:

是否所有内置值类型的默认值的哈希码都为 0? 如果不是这样,我会感到非常惊讶,但我不想保证。 另外,default(T) 和 default(K) 不起作用“不能将 '==' 应用于类型 'T' 和 'T'”【参考方案5】:

首先,这段代码违反了 SRP 原则。 Pair 类用于保存物品,对吧?将相等性比较功能委托给它是不正确的。

接下来让我们看看你的代码:

如果参数之一为空,Equals 方法将失败 - 不好。 Equals 使用 Pair 类的哈希码,但看一下 GetHashCode 的定义,它只是对成员哈希码的组合——它与项目的相等性无关。我希望 Equals 方法将比较实际数据。不幸的是,我现在太忙了,无法提供正确的实施。但是乍一看,您的代码似乎是错误的。如果您向我们提供您想要实现的目标的描述会更好。相信SO成员一定能给你一些建议。

【讨论】:

首先,在正常的代码流中,Equals 方法如何接受空参数?其次,哈希码可以用于相等。事实上,这就是它最常用的(比较项目)。这段代码的唯一问题是 First 和 Second 可能不是不可变的。 我很抱歉。两个相等的对象共享相同的hashcode,但是具有相同hashcode的两个对象可能不相等。 “不可变对”类持有不可变的一对项目并不违反 SRP,并实现等价关系,即如果两对中的对应项目等价,则对本身也是等价的。所有类型都应该有一个定义的等价关系;可变引用类型应该使用默认的引用-等价关系,而不可变类型和值类型通常应该认为它们自己是等价的,如果相应的字段被认为是等价的。【参考方案6】:

我可以建议使用 Lambda 表达式作为参数吗? 这将允许您指定如何比较内部泛型类型。

【讨论】:

【参考方案7】:

编译时我没有收到任何警告,但我假设您正在谈论 == null 比较?演员阵容似乎会让这一切变得更干净,是的。

PS。你真的应该为比较器使用一个单独的类。填补两个角色(作为一对和比较对)的这个类简直丑陋。

【讨论】:

以上是关于如何在不可变的泛型 Pair 结构上实现 IEqualityComparer?的主要内容,如果未能解决你的问题,请参考以下文章

Java的泛型约束和限制

java的泛型

如何比较 C++ 中的泛型结构?

13.3.5 接口和委托的泛型可变性限制和说明

java中的泛型 求详细解释

为啥接口的泛型方法可以在 Java 中实现为非泛型?