【问题标题】:How do you implement GetHashCode for structure with two string, when both strings are interchangeable当两个字符串可以互换时,如何为具有两个字符串的结构实现 GetHashCode
【发布时间】:2010-09-09 09:02:33
【问题描述】:

我在 C# 中有一个结构:

public struct UserInfo
{
   public string str1
   {
     get;
     set;
   }

   public string str2
   {
     get;
     set;
   }   
}

唯一的规则是UserInfo(str1="AA", str2="BB").Equals(UserInfo(str1="BB", str2="AA"))

如何覆盖此结构的 GetHashCode 函数?

【问题讨论】:

标签: c# hashtable


【解决方案1】:

MSDN:

哈希函数必须具有以下属性:

  • 如果两个对象比较相等,则每个对象的GetHashCode 方法必须返回相同的值。但是,如果两个对象比较不相等,则两个对象的 GetHashCode 方法不必返回不同的值。
  • 对象的GetHashCode 方法必须一致地返回相同的哈希码,只要没有修改确定对象Equals 方法返回值的对象状态。请注意,这仅适用于应用程序的当前执行,如果再次运行应用程序,可能会返回不同的哈希码。
  • 为了获得最佳性能,哈希函数必须为所有输入生成随机分布。

考虑到正确的方法是:

return str1.GetHashCode() ^ str2.GetHashCode() 

^ 可以用其他交换运算代替

【讨论】:

  • 不应该是 return str1.GetHashCode() ^ str2.GetHashCode();
  • 另外,不考虑空值。
  • Omer van Kloeten,对于任何 .net 开发人员来说都应该是显而易见的。快速示例旨在显示一般想法,而不是完整的解决方案
  • 如果您希望哈希中的 str1,str2 和 str2,str1 非常频繁地出现,则查找速度可能会比应有的速度慢一些。还可以通过缓存哈希码来提高查找速度。显然,这些可能是过早的优化。
  • +1 指出使用交换运算的重要性
【解决方案2】:

参见Jon Skeet's answer - 像^ 这样的二元运算不好,它们经常会产生碰撞哈希!

【讨论】:

  • 但是 jon 说这很糟糕,因为它会完全按照 OP 的要求做。 F(a,b) == F(b,a) ...
【解决方案3】:
public override int GetHashCode()
{
    unchecked
    {
        return (str1 ?? String.Empty).GetHashCode() +
            (str2 ?? String.Empty).GetHashCode();
    }
}

使用 '+' 运算符可能比使用 '^' 更好,因为尽管您明确希望 ('AA', 'BB') 和 ('BB', 'AA') 明确相同,但您可能不希望 ('AA', 'AA') 和 ('BB', 'BB') 相同(或所有相等的对)。

此解决方案并未完全遵守“尽可能快”规则,因为在空字符串的情况下,它会在空字符串上执行“GetHashCode()”,而不是立即返回已知常量,但即使没有显式测量我愿意冒险猜测,除非您预计会有很多空值,否则差异不会大到可以担心。

【讨论】:

    【解决方案4】:
    1. 作为一般规则,为类生成哈希码的一种简单方法是对所有可以参与生成哈希码的数据字段进行异或(注意检查其他人指出的 null)。这也满足了 UserInfo("AA", "BB") 和 UserInfo("BB", "AA") 的哈希码相同的(人为?)要求。

    2. 如果你可以对你的类的使用做出假设,你也许可以改进你的散列函数。例如,如果 str1 和 str2 相同是常见的,那么 XOR 可能不是一个好的选择。但是如果 str1 和 str2 代表名字和姓氏,XOR 可能是一个不错的选择。

    虽然这显然不是一个真实的例子,但可能值得指出的是: - 这可能是使用结构的一个糟糕示例:结构通常应该具有值语义,但这里似乎并非如此。 - 使用带有 setter 的属性来生成哈希码也是自找麻烦。

    【讨论】:

    • 嗯,为什么你认为他的结构没有值语义?你能扩展你的最后一句话吗?
    【解决方案5】:

    按照 ReSharper 的建议:

    public int GetHashCode()
    {
        unchecked
        {
            int hashCode;
    
            // String properties
            hashCode = (hashCode * 397) ^ (str1!= null ? str1.GetHashCode() : 0);
            hashCode = (hashCode * 397) ^ (str2!= null ? str1.GetHashCode() : 0);
    
            // int properties
            hashCode = (hashCode * 397) ^ intProperty;
            return hashCode;
        }
    }
    

    397 是一个足以导致结果变量溢出并在一定程度上混合散列位的素数,从而提供更好的散列码分布。否则 397 并没有什么特别之处可以将它与其他相同大小的素数区分开来。

    【讨论】:

    • 此哈希码不满足 OP 的要求:唯一的规则是 UserInfo(str1="AA", str2="BB").Equals(UserInfo(str1="BB", str2=" AA"))
    【解决方案6】:

    一个简单的通用方法是这样做:

    return string.Format("{0}/{1}", str1, str2).GetHashCode();
    

    除非您有严格的性能要求,否则这是我能想到的最简单的方法,并且当我需要复合键时,我经常使用此方法。它可以很好地处理null 的情况,并且不会导致(m)任何哈希冲突(通常)。如果您希望字符串中有“/”,只需选择另一个您不希望的分隔符。

    【讨论】:

    • 确实很简单。这可以在 C# 6.0 中简化为 return $"{str1}/{str2}".GetHashCode();。见String Interpolation
    • 不安全,如果 str1 = "a/b" 和 str2 = "" 怎么办?这将具有与 str1 = "a" 和 str2 = "b/" 相同的哈希值。
    • @ErwinMayer 使用您知道不在字符串中的分隔符。此外,GetHashCode 不需要总是返回唯一值。它被用作一种优化以避免过于频繁地调用Equals(精确比较通常更昂贵)。
    • 如何确保它为 str1="a"、str2="b" 和 str1="b" str2="a" 生成相同的哈希码?是否有一些魔法可以使“a/b”和“b/a”产生相同的哈希?
    • @KaspervandenBerg 不,这两个必须有不同的哈希,因为它们不一样,对吧?
    【解决方案7】:
    public override int GetHashCode()   
    {       
        unchecked      
        {           
            return(str1 != null ? str1.GetHashCode() : 0) ^ (str2 != null ? str2.GetHashCode() : 0);       
        }   
    }
    

    【讨论】:

    • 为什么不勾选? xor 不能溢出。
    【解决方案8】:

    是的,正如 Gary Shutler 指出的那样:

    return str1.GetHashCode() + str2.GetHashCode();
    

    可以溢出。您可以尝试按照 Artem 的建议强制转换为 long,或者您可以将语句括在 unchecked 关键字中:

    return unchecked(str1.GetHashCode() + str2.GetHashCode());
    

    【讨论】:

      【解决方案9】:

      试试这个:

      (((long)str1.GetHashCode()) + ((long)str2.GetHashCode())).GetHashCode()
      

      【讨论】:

        【解决方案10】:

        许多可能性。例如

        return str1.GetHashCode() ^ str1.GetHashCode()

        【讨论】:

          【解决方案11】:

          也许类似于 str1.GetHashCode() + str2.GetHashCode()?或 (str1.GetHashCode() + str2.GetHashCode()) / 2?这样无论str1和str2是否交换都是一样的......

          【讨论】:

            【解决方案12】:

            对它们进行排序,然后将它们连接起来:

            返回 ((str1.CompareTo(str2)

            【讨论】:

            • 这将导致您的 GetHashCode 方法执行大量工作。哈希码旨在快速。来自 MSDN:“哈希函数用于快速生成与对象值对应的数字(哈希码)”。在散列函数中分配新字符串似乎是个坏主意。
            【解决方案13】:

            GetHashCode 的结果应该是:

            1. 尽可能快。
            2. 尽可能独特。

            考虑到这些,我会选择这样的东西:

            if (str1 == null)
                if (str2 == null)
                    return 0;
                else
                   return str2.GetHashCode();
            else
                if (str2 == null)
                    return str1.GetHashCode();
                else
                   return ((ulong)str1.GetHashCode() | ((ulong)str2.GetHashCode() << 32)).GetHashCode();
            

            编辑:忘记了空值。代码已修复。

            【讨论】:

            • 唯一的规则是 UserInfo(str1="AA", str2="BB").Equals(UserInfo(str1="BB", str2="AA"))
            【解决方案14】:

            从 C# 7 开始,我们可以利用 ValueTuple:

            return (str1, str2).GetHashCode();
            

            【讨论】:

            • 但是你确定 (str1,str2).GetHashCode() 和 (str2,str1).GetHashCode() 一样吗?
            • 这不是必需的,也使用其他算法,有时您在异或之前对其中一个字段(例如str1
            【解决方案15】:

            太复杂了,会忘记空值等。这用于分桶之类的事情,所以你可以摆脱类似的事情

            if (null != str1) {
                return str1.GetHashCode();
            }
            if (null != str2) {
                return str2.GetHashCode();
            }
            //Not sure what you would put here, some constant value will do
            return 0;
            

            这是有偏见的,因为假设 str1 在异常大比例的实例中不太常见。

            【讨论】:

            • 这不满足str1和str2的顺序无关的条件。 ("A", "B") 和 ("B", "A") 产生不同的哈希码。
            • 6.5 年后?你指的是什么条件?这是为包含 2 个字符串的结构生成哈希码的讨论,而不是比较 2 个字符串时发生的情况。
            • 结构体 ("A", "B") 和 ("B", "A") 应该被认为是相等的。因此,它们的哈希码必须相等。但是 ("A", "B") 产生了 "A" 的哈希码,而 ("B", "A") 产生了 "B" 的哈希码 - 不相等。
            • 鉴于此问题至少在过去 6 个月内已被编辑,我不确定最初是否在此问题中。
            猜你喜欢
            • 2015-07-16
            • 1970-01-01
            • 2021-12-25
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-11-14
            相关资源
            最近更新 更多