使用等于。 GetHashCode 不是

Equals is used. GetHashCode is not

我已经实现了下面的class:

public class carComparer : IEqualityComparer<Car>
    {
        public bool Equals(Car car1, Car car2)
        {
                if (car1 == null || car2 == null)
                    return false;

                return (car1.description == car2.description);
        }

        public int GetHashCode(Car car)
        {
            unchecked 
            {
                int hash = 17;
                hash = hash * 29 + car.id.GetHashCode();
                hash = hash * 29 + car.description.GetHashCode();
                return hash;
            }
        }

    }

现在看这个:

Car p1 = new Car() { id = Guid.NewGuid(), description = "Test1" };
        Car p2 = new Car() { id = Guid.NewGuid(), description = "Test1" };
        Car p3 = new Car() { id = Guid.NewGuid(), description = "Test1" };
        Car p4 = new Car() { id = Guid.NewGuid(), description = "Test1" };
        var hash = new HashSet<Car>();
        hash.Add(p1);
        hash.Add(p2);

        var hash2 = new HashSet<Car>();
        hash2.Add(p3);
        hash2.Add(p4);

        var carComparer = new CarComparer();
        Assert.That(hash, Is.EquivalentTo(hash2).Using(carComparer));

我在 .equals 和 .hashcode 中设置了断点。使用等于;但 GetHashCode 不是。为什么?

GetHashCode 通常用于哈希-table-查找。

GetHashCode 不必保证唯一,因此不是有效的 IsEqual 测试。

要使用 GetHashCode,请使用 HashSet 的此构造函数:

https://msdn.microsoft.com/en-us/library/bb359100(v=vs.110).aspx

因此,为了使用 GetHashCode 方法,您需要使用:

var hash = new HashSet<Car>(carComparer);

请注意,将您的对象添加到哈希集时将验证哈希。

然后在 HashSet.Add 方法中使用比较器,在此调用中:

private int InternalGetHashCode(T item)
{
  if ((object) item == null)
    return 0;

  //this._comparer is set during the constructor call
  return this._comparer.GetHashCode(item) & int.MaxValue;
}

由于显而易见的原因,这使得 Comparer 成为只读的 属性。

所以,总结一下;

未使用 GetHashCode,因为它通常用于哈希-table-查找创建,您需要在开始添加项目之前将其提供给 HashSet .

由于显而易见的原因,使用了IsEqual;如果不是:请参阅@dasblinkenlight 的回答。

发生这种情况是因为 IsEquivalent 中使用的算法来决定等效性:实现从您期望的 collection 构造他们所谓的 "collection tally" object,然后尝试从中删除实际 collection 的项目 one-by-one:

public bool TryRemove(IEnumerable c) {
    foreach (object o in c)
        if (!TryRemove(o))
            return false;
    return true;
}

public bool TryRemove(object o) {
    for (int index = 0; index < list.Count; index++)
        if (ItemsEqual(list[index], o)) {
            list.RemoveAt(index);
            return true;
        }
    return false;
}

可以看到NUnit使用了相对低效的O(n2)算法,而不是构造一个O(n)效率的哈希集。这对于较大的集合很重要,但由于单元测试中的典型 collection 只有几个项目,因此不会有明显的差异。

ItemsEqual 使用相等比较器中的 Equals,但它不需要散列码功能 (source code)。

您正在使用 NUnit Is.EquivalentTo 比较两个 HashSet。它没有理由调用 GetHashCode - 它基本上比较两个集合的成员是否相等。这就是为什么从不调用 GetHashCode 而调用 Equals 来比较来自不同 HashSet 的两个项目是否相等。您的哈希集也可以是列表或任何其他可枚举的 - 在比较两个集合时不会改变任何内容。

您可能希望在将项目添加到 HashSet 时调用 GetHashCode - 但事实并非如此,因为此时您的 carComparer 尚不清楚 - 您不知道不要将它传递给 HashSet 构造函数。如果你会这样做:

var hash = new HashSet<Car>(new carComparer());

然后GetHashCode将在您向相应的HashSet添加新项目时被调用。