【问题标题】:Overriding Object.Equals() instance method in C#; now Code Analysis/FxCop warning CA2218: "should also redefine GetHashCode". Should I suppress this?在 C# 中覆盖 Object.Equals() 实例方法;现在代码分析/FxCop 警告 CA2218:“也应该重新定义 GetHashCode”。我应该压制这个吗?
【发布时间】:2010-03-25 14:44:03
【问题描述】:

我的 C# 项目中有一个复杂的类,我希望能够对它进行相等性测试。它不是一个微不足道的类;它包含各种标量属性以及对其他对象和集合的引用(例如 IDictionary)。不管怎样,我的班级是封闭的。

为了在我的系统的其他地方启用性能优化(一种避免代价高昂的网络往返的优化),我需要能够将这些对象的实例相互比较以获得相等性——除了内置的引用相等性– 所以我重写了 Object.Equals() 实例方法。然而,既然我已经这样做了,Visual Studio 2008 的代码分析(又名 FxCop)(我默认保持启用)会发出以下警告:

警告:CA2218:Microsoft.Usage:自“MySuperDuperClass”以来 重新定义 Equals,它也应该重新定义 GetHashCode。

我想我理解这个警告的基本原理: 如果我要在集合中使用像 key 这样的对象,哈希码很重要。即see this question。 但是,我不会将这些对象用作集合中的键。永远。

觉得有理由取消警告,我查找了code CA2218 in the MSDN documentation 以获取警告的全名,因此我可以将SuppressMessage 属性应用于我的类,如下所示:

    [SuppressMessage("Microsoft.Naming",
        "CA2218:OverrideGetHashCodeOnOverridingEquals",
        Justification="This class is not to be used as key in a hashtable.")]

但是,在进一步阅读时,我注意到以下内容:

如何解决违规问题

要修正违反此规则的行为, 提供一个实现 获取哈希码。对于一对对象 相同的类型,您必须确保 实现返回相同 如果您实施 Equals,则价值 为该对返回 true。

何时取消警告

-----> 不要抑制由此产生的警告 规则。 [箭头和强调我的]

所以,我想知道:为什么我不应该像我计划的那样隐藏这个警告?我的案子难道不应该被禁止吗?我不想为这个永远不会被调用的对象编写 GetHashCode() 的实现,因为我的对象永远不会成为集合中的键。如果我想变得迂腐而不是压抑,那么用引发 NotImplementedException 的实现覆盖 GetHashCode() 对我来说是否更合理?


更新: 我刚刚在 Bill Wagner 的好书 Effective C# 中再次查阅了这个主题,他在“第 10 项:理解 GetHashCode() 的陷阱”中指出:

如果您定义的类型不会 曾经被用作一个关键 容器,这无关紧要。类型 代表窗口控件、web 页面控件或数据库连接 不太可能被用作 收藏。在这些情况下,做 没有。所有引用类型都将 有一个正确的哈希码,甚至 如果它非常低效。 [...] 在 您创建的大多数类型,最好的 方法是避免存在 GetHashCode() 完全。

...这就是我最初得到这个想法的地方,我不需要总是关心 GetHashCode()。

【问题讨论】:

    标签: c# warnings fxcop suppress-warnings


    【解决方案1】:

    如果你是真的肯定你永远不会使用这个东西作为哈希表的键,那么你的提议是合理的。 重写 GetHashCode;让它抛出一个异常。

    请注意,哈希表隐藏在不太可能的地方。许多 LINQ 序列运算符在内部使用哈希表实现来加快速度。通过拒绝 GetHashCode 的实现,您也拒绝能够在各种 LINQ 查询中使用您的类型。我喜欢构建使用记忆来提高速度的算法; memoizers 通常使用哈希表。因此,您还拒绝记忆将您的类型作为参数的方法调用的能力。

    或者,如果您不想那么苛刻:Override GetHashCode;使其始终返回零。 满足 GetHashCode 的语义要求;两个相等的对象总是有相同的哈希码。如果它曾经被用作字典中的键,性能将会很糟糕,但是当它出现时你可以处理这个问题,你声称它永远不会。

    说了这么多:来吧。您输入问题所花费的时间可能比正确实施问题所花费的时间更多。去做吧。

    【讨论】:

    • 好答案。并且回复:“您输入问题所花费的时间可能比正确实施它所花费的时间更多。” ...您是对的在这种情况下但是我实际上有一个 series 的 N 个对象,需要 Equals() 之类的东西来支持我的优化。试图避免 N* 的工作。 ;-)
    • 特别感谢您指出 LINQ 可能暗示使用 GetHashCode()。这特别有启发性。
    【解决方案2】:

    你不应该压制它。看看你的 equals 方法是如何实现的。我确信它会比较班级中的一个或多个成员来确定平等。这些成员之一通常足以将一个对象与另一个对象区分开来,因此您可以通过返回membername.GetHashCode(); 来实现GetHashCode。

    【讨论】:

    • 它实际上比较了类的几乎所有成员以确定相等性。该类包含一系列计算的假设。它们中的任何一个不同就足以让我认为对象不同。一些假设是其他数字的数组或集合。因此,如果 GetHashCode 被(正确地)实现,它必然同样复杂。
    • @Chris:不必如此。一个简单但通常可以接受的实现是对成员的 GetHashCode 值进行异或。
    • 一种更简单且可接受(但通常不是最佳)的实现是仅返回一个字段的 GetHashCode() 值。这就是结构所发生的事情。
    • @klasbyskov、@Steven Sudit、@nobugz:是的,我刚刚意识到我的幼稚:GetHashCode()不一定需要考虑所有 i> 由 Equals() 引用的成员中的一个——它们中的数量刚好足以作为哈希码“高效”。当 Eric Lippert 提到最简单/最坏的情况时,这一点对我来说很明显:*“如果你不想那么苛刻:覆盖 GetHashCode;让它总是返回零。”
    • 我接受这个作为答案。埃里克·利珀特助攻。
    【解决方案3】:

    我的 0.10 美元值多少钱?实现 GetHashCode。

    尽管您说您永远不会需要它,但您可能会改变主意,或者其他人可能对如何使用代码有其他想法。一个有效的 GetHashCode 不难制作,并且保证以后不会有任何问题。

    【讨论】:

    • 如果我用 “不要将它用作集合中的键” 记录我的课程,那么抛出 NotImplementedException 用于我班级的 GetHashCode()?这种方法有什么问题?
    • 你可以,但真正的好处是什么?具有特殊限制的类在放入 Dictionary 时会引发异常,这是一种责任。实施 GetHashCode 使其符合我们的合理预期。
    【解决方案4】:

    一旦你忘记了,或者其他不知道的开发人员使用了这个,就会有人要追踪一个痛苦的错误。我建议您简单地正确实现 GetHashCode,然后您就不必担心它了。或者只是不要将 Equals 用于您的特殊相等比较案例。

    【讨论】:

    • 但是,我上一段中的想法如何:实现 GetHashCode() 以抛出 NotImplementedException?开发人员更难误用它作为密钥 - 它会爆炸(按设计)。
    • 虽然它可以减轻这个错误,但最好现在而不是以后定义语义。
    【解决方案5】:

    GetHashCode 和 Equals 方法协同工作,为您的类型提供基于值的相等语义 - 您应该一起实现它们。

    有关此主题的更多信息,请参阅以下文章:

    无耻插件:这些文章是我写的。

    【讨论】:

    • GetHashCode() 是否会被框架调用,不包括将对象用作集合中的键时?
    • 我不确定,但要记住的重要一点是,在未来的任何时候它可以被调用,因为当前的建议是你实现它会的方法最好这样做。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-12-15
    • 2016-08-08
    • 2023-03-24
    • 1970-01-01
    • 2014-01-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多