【问题标题】:C# How to select a Hashcode for a class that violates the Equals contract?C# 如何为违反 Equals 合约的类选择 Hashcode?
【发布时间】:2009-05-07 15:24:42
【问题描述】:

由于某些原因,我有多个课程不遵循官方Equals 合同。在被覆盖的GetHashCode() 中,这些类只返回 0,以便它们可以在 Hashmap 中使用。

其中一些类实现了相同的接口,并且有使用此接口作为键的 Hashmap。所以我认为每个类至少应该在GetHashCode() 中返回一个不同的(但仍然是不变的)值。

问题是如何选择这个值。我应该简单地让第一个类返回 1,下一个类返回 2,依此类推吗?或者我应该尝试类似的东西

class SomeClass : SomeInterface {
    public overwrite int GetHashCode() {
        return "SomeClass".GetHashCode();
    }
}

所以哈希分布更均匀? (我必须自己缓存返回值还是微软的编译器能够优化这个?)

更新:不可能为每个对象返回单独的哈希码,因为 Equals 违反了合同。具体来说,我指的是this problem

【问题讨论】:

    标签: c# hash equals gethashcode


    【解决方案1】:

    如果它“违反了 Equals 合同”,那么我不确定您是否应该将其用作密钥。

    它使用它作为键,你真的需要得到正确的散列......目前还不清楚Equals 的逻辑是什么,但是两个被认为相等的值必须具有相同的哈希码。不要求具有相同哈希码的两个值相等。

    使用常量字符串并没有太大帮助 - 您将在类型上平均分配值,但仅此而已...

    【讨论】:

    • 你是绝对正确的,将它用作键是一个坏主意。但是,在我的具体情况下,它是有效的,正如对 Strilanc 帖子的评论中所解释的那样。 - 是的,它没有多大帮助,但我认为它可能仍然比没有好... :-\
    【解决方案2】:

    我很好奇重写GetHashCode() 并返回一个常量值的原因是什么。为什么要违反哈希的概念,而不是仅仅违反“合同”而不是根本不覆盖 GetHashCode() 函数并保留 Object 的默认实现?

    编辑

    如果您这样做是为了让您的对象根据它们的内容而不是它们的引用进行匹配,那么您建议让不同的类简单地使用不同的常量就可以工作,但效率非常低。您要做的是提出一种散列算法,该算法可以获取您的类的内容并产生一个平衡速度与均匀分布的值(即散列 101)。

    我想我不确定你在寻找什么......没有一个“好的”方案来为这个范例选择常数。一个并不比另一个好。尝试改进您的对象,以便创建真正的哈希。

    【讨论】:

    • 如果我没记错的话,保留默认实现可能会导致具有不同哈希的“相等”对象。这真的会弄乱我的程序,所以我宁愿不这样做。
    【解决方案3】:

    我在编写向量类时遇到了这个确切的问题。我想比较向量是否相等,但浮点运算会产生舍入误差,所以我想要近似相等。长话短说,除非你的实现是对称的、自反的和传递的,否则重写 equals 是一个坏主意。

    其他类将假设 equals 具有这些属性,使用这些类的类也将如此,因此您可能会遇到奇怪的情况。例如,一个列表可能会强制执行唯一性,但最终会得到两个评估为等于某个元素 B 的元素。

    哈希表是您破坏相等时不可预测行为的完美示例。例如:

    //Assume a == b, b == c, but a != c
    var T = new Dictionary<YourType, int>()
    T[a] = 0
    T[c] = 1
    return T[b] //0 or 1? who knows!
    

    另一个例子是一个集合:

    //Assume a == b, b == c, but a != c
    var T = new HashSet<YourType>()
    T.Add(a)
    T.Add(c)
    if (T.contains(b)) then T.remove(b)
    //surely T can't contain b anymore! I sure hope no one breaks the properties of equality!
    if (T.contains(b)) then throw new Exception()
    

    我建议使用另一种方法,名称类似于 ApproxEquals。您还可以考虑覆盖 == 运算符,因为它不是虚拟的,因此不会被其他类(如 Equals)意外使用。

    如果您确实不能对哈希表使用引用相等,请不要破坏可以使用的情况的性能。添加一个 IApproxEquals 接口,在您的类中实现它,并向 Dictionary 添加一个扩展方法 GetApprox,该方法枚举查找近似相等的键,并返回关联的值。您还可以为 3 维向量或任何您需要的东西编写自定义字典。

    【讨论】:

    • - 如果你不介意,因为我们似乎有同样的问题,你能告诉我更多关于你的故事吗? - 我仔细设计了我的代码,以意识到向量重合的可能性。到目前为止,一切正常。 - 我不确定我是否正确理解了你的最后一段。我看到您将 Equals 保留为由 Object 继承并添加自定义 ApproxEquals 的想法。但是“需要在编译时知道类型”是什么意思?
    • 我的意思是 Equals 是虚拟的,而 == 不是。这会对它们的使用方式产生影响。这里重要的是,其他类不会意外使用你奇怪的 == 运算符,但他们可以使用你奇怪的 Equals。在我的项目中,我刚刚实现了 == 运算符,到目前为止它运行良好。每当我使用列表或表格时,我发现引用相等是我想要的,或者至少等同于我想要的。
    • 关于 Hashtable 示例:我目前认为这种不确定性是设计的一部分,这听起来很恶心。还有其他无法修复的漏洞(多次添加“相等”但不同的值到哈希表会导致原始值的巨大偏差)。幸运的是,这些情况不会出现在程序中。不过,我会非常仔细地查看您的 ApproxEquals 想法。
    • 您不应该破坏您将具有引用相等性的情况,以便您可以将哈希表与您没有的情况一起使用。写一个专为向量设计的哈希表,或者在字典中添加一个方法来处理近似等于。
    • 如果你想测试近似相等,我相信“测试 8 个(或其他)有效数字的相等性”将是自反的、对称的和传递的,而“百分比差异”方法则不会。
    【解决方案4】:

    当发生哈希冲突时,HashTable/Dictionary 会调用 Equals 来查找您要查找的键。使用常量哈希码首先消除了使用哈希的速度优势——它变成了线性搜索。

    你是说 Equals 方法没有按照合同实现。你到底是什么意思?根据违规类型,HashTable 或 Dictionary 只会很慢(线性搜索)或根本不起作用。

    【讨论】:

    • 确实,很可能许多对象共享相同的散列。然而,由于某些原因,情况并非总是如此,字典查找应该(没有测量)仍然有一点优势。
    猜你喜欢
    • 1970-01-01
    • 2011-07-09
    • 1970-01-01
    • 1970-01-01
    • 2010-09-16
    • 1970-01-01
    • 2018-07-15
    • 1970-01-01
    • 2017-10-15
    相关资源
    最近更新 更多