如何在不可变的通用Pair结构上实现IEqualityComparer?

时间:2020-03-06 14:35:47  来源:igfitidea点击:

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

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进行比较。

解决方案

IEqualityComparer实现应该是一个不同的类(要重用该引用,绝对不能是一个结构)。

同样,哈希码永远都不应被缓存,因为结构的默认GetHashcode实现(我们不会重写)将考虑该成员。

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

我看不到我们要实现的目标,但是我们不应该使用哈希码进行相等性比较,不能保证两个不同的对象不会具有相同的哈希码。同样,即使结构是不可变的,成员_first和_second也不是。

首先,此代码违反了SRP原则。配对类,用于容纳成对的物品,对吗?委托相等性比较功能是不正确的。

接下来让我们看一下代码:

如果参数之一为null,Equals方法将失败。 Equals使用Pair类的哈希码,但请看一下GetHashCode的定义,它只是Pair成员哈希码的组合,与项目的相等性无关。我希望Equals方法会比较实际数据。不幸的是,我现在很忙,无法提供正确的实现。但是从第一眼看,代码似乎是错误的。如果我们向我们提供我们要达到的目标的描述,那将更好。我敢肯定,SO成员将能够为我们提供一些建议。

我是否建议将Lambda表达式用作参数?
这将允许我们指定如何比较内部泛型类型。

对此进行编译时,我没有收到任何警告,但我假设我们正在谈论== null比较?强制转换似乎可以使这一切变得更干净,是的。

PS。我们确实应该为比较器使用一个单独的类。这个扮演两个角色(成对和比较对)的类很丑陋。

如果在比较方法中使用哈希码,则如果哈希码相同,则应检查"实际值"。

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"字段或者对象的"等于"方法。还是更复杂的东西?

看来我们需要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);
    }
  }
}