我已经构建了一些复杂的对象,我试图通过进行一些单元测试来验证它是否正常工作。这涉及比较一些List(Of T),所以我尝试使用CollectionAssert。 现在我遇到了一些奇怪的东西。
首先,我使用CollectionAssert.AreEqual来查看第一个列表是否相等。这个断言过去了。但为了简单起见,我想使用CollectionAssert.AreEqual,这样我就不必按照正确的顺序创建预期的对象,所以我开始尝试。使用完全相同的代码,CollectionAssert.AreEquivalent失败。我会说这很奇怪,因为等价是一个比平等更宽松的断言,对吗?我收到这个错误:
CollectionAssert.AreEquivalent failed. The expected collection contains 1 occurrence(s) of <MyObject>. The actual collection contains 0 occurrence(s).
我尝试过调试,但是我没有让它调试.Net框架,即使我设置了下载符号文件。所以我只能看到它进入我的自定义Equals函数 - 它返回true - 然后断言失败。两个对象都有两个元素。调用堆栈(按相反顺序):
现在我正在写这个,一个想法出现了,我发现了一个潜在的问题。我看到它在内部使用了一个字典。哪个可能作为某种hashmap,其中int是实际列表中的索引?这是否意味着我需要实现自定义IEqualityComparer,而不是覆盖equals?那我的getHashCode()应该怎么样呢? (我猜这是至关重要的,因为我认为它可能用于字典中的密钥?)
答案 0 :(得分:2)
您走在正确的轨道上:问题确实是您在覆盖GetHashCode
时未覆盖Equals
。
以下是重现问题的示例:
void Main()
{
var a = new []{new Broken{Foo="a"}, new Broken{Foo="b"}};
var b = new []{new Broken{Foo="a"}, new Broken{Foo="b"}};
CollectionAssert.AreEqual(a, b);
CollectionAssert.AreEquivalent(a, b);
}
class Broken
{
public string Foo {get;set;}
public override bool Equals(object obj)
{
return Foo == ((Broken)obj).Foo;
}
}
正如您已正确指出的那样,CollectionAssert.AreEquivalent
使用Dictionary
,它用于计算每个唯一元素在集合中的频率。
问题不在于哈希码可能会发生冲突,但是如果Equals
返回的哈希码不同,那么如果两个应该被视为相等的元素实际上从未使用GetHashCode
进行比较。
您可能也会对这个问题感兴趣:
Why is it important to override GetHashCode when Equals method is overridden?
另外,我的equals函数中有一些逻辑,它不直接比较所有字符串1对1,所以这意味着我基本上会实现这个功能两次,以确保相等的对象获得相同的哈希码,对吧?
不一定。 Dictionary
的性能取决于散列算法(当用作密钥时,对象的散列值也不应更改)。
如果您可以忍受一些性能损失(可能忽略不计),您可以使用更简单的方法来计算哈希值,而不是在Equals
方法中使用(并接受更多哈希冲突)。如果两个对象的哈希值相等,则无论如何都会调用Equals
。 (事实上,你可以随时返回相同的值,例如每次1)。
文件中的相关部分:
在Hashtable对象中用作键的对象还必须覆盖GetHashCode方法,因为这些对象必须生成自己的哈希码
如果两个对象比较相等,则每个对象的GetHashCode方法必须返回相同的值。但是,如果两个对象的比较不相等,则两个对象的GetHashCode方法不必返回不同的值。