简化C#中的Equals()和GetHashCode()重写以提高可维护性

9

我发现自己经常覆盖 Equals()GetHashCode() 方法,以实现具有相同属性值的业务对象相等的语义。这会导致编写重复且难以维护的代码(如果添加属性,则一个或两个方法都未更新)。

最终代码看起来像这样(欢迎对实现进行评论):

public override bool Equals(object obj)
{
    if (object.ReferenceEquals(this, obj)) return true;

    MyDerived other = obj as MyDerived;

    if (other == null) return false;

    bool baseEquals = base.Equals((MyBase)other);
    return (baseEquals && 
        this.MyIntProp == other.MyIntProp && 
        this.MyStringProp == other.MyStringProp && 
        this.MyCollectionProp.IsEquivalentTo(other.MyCollectionProp) && // See https://dev59.com/jHVD5IYBdhLWcg3wNY1Z#9658866
        this.MyContainedClass.Equals(other.MyContainedClass));
}

public override int GetHashCode()
{
    int hashOfMyCollectionProp = 0;
    // http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/
    // BUT... is it worth the extra math given that elem.GetHashCode() should be well-distributed?
    int bitSpreader = 31; 
    foreach (var elem in MyCollectionProp)
    {
        hashOfMyCollectionProp = spreader * elem.GetHashCode();
        bitSpreader *= 31;
    }
    return base.GetHashCode() ^ // ^ is a good combiner IF the combined values are well distributed
        MyIntProp.GetHashCode() ^ 
        (MyStringProp == null ? 0 : MyStringProp.GetHashValue()) ^
        (MyContainedClass == null ? 0 : MyContainedClass.GetHashValue()) ^
        hashOfMyCollectionProp;
}

我的问题

  1. 这个实现模式是否正确?
  2. 如果贡献部件的值分布良好,那么是否^足够?在组合集合元素时,是否需要乘以31的N次方,给定它们的哈希是分布均匀的?
  3. 看起来这段代码可以抽象出使用反射确定公共属性、构建与手写解决方案匹配的表达式树,并根据需要执行表达式树的代码。这种方法是否合理?是否已经有现成实现?

2
为什么在发布一年后还会被踩?这个问题非常合理。如果有什么不对的地方,请指出来。 - Eric J.
3个回答

4

MSDN实际上并没有说“不要为可变类型重载Equals等方法”。虽然曾经有这样的说法,但现在它说:

当您定义一个类或结构时,您需要决定是否有意义为该类型创建自定义的值相等(或等效)定义。通常,在期望将该类型的对象添加到某种集合中或其主要目的是存储一组字段或属性时,会实现值相等性。

http://msdn.microsoft.com/en-us/library/dd183755.aspx

然而,在对象参与哈希集合(Dictionary<T,U>HashSet<T>等)时,哈希码的稳定性存在复杂性。

我决定选择最好的两种方法,如下所述:

https://dev59.com/jGkw5IYBdhLWcg3ws8tj#9752155


1
我发现自己经常重写Equals()和GetHashCode()函数
MSDN表示:不要为可变类型重载Equals等函数
给定贡献组件值的分布良好,是否^足够?
是的,但它们并不总是均匀分布的。考虑int属性。建议使用一些(小)质数进行移位。

MSDN在哪里说了那个?我尝试了字面上的谷歌搜索,只发现了这个问题。 - Eric J.
关于我的情况中的^...,每个XOR值都是GetHashCode()的结果,应该是合理分布的。就实现而言,您是否认为我在使用^时存在缺陷?即使每个集合元素的哈希是GetHashCode()的结果,您是否会将集合元素乘以质数? - Eric J.
啊...直到你指出来,我才意识到int的哈希码就是int本身。我不明白为什么“在非不可变类型中重写运算符==是不推荐的”是普遍适用的(也许这就是为什么MSDN从官方文档中删除了它?)。不过那听起来更像是一个单独的问题,所以我很快会提出一个问题。 - Eric J.
更好的问题是:你的对象是否具有身份标识。如果是,则引用相等是完美的实现。 - H H
@HenkHolterman:很遗憾,微软从未定义可变对象的标准约定,以允许比较它们的状态。在我看来,Equals重载应该定义严格的等价性,这样可见的可变对象除了自身之外,永远不会与任何东西等价,但是如果它们持有对具有匹配状态的对象实例的引用,则持有对可变类型实例的引用但永远不会被改变的对象可能是等价的。 - supercat
显示剩余4条评论

0

也许我有点困惑,但是在 GetHashCode 重写中,on null 检查应该返回1而不是0,对吗?

所以

MyStringProp == null ? 0 : MyStringProp.GetHashValue()

应该是

MyStringProp == null ? 1 : MyStringProp.GetHashValue()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接