71

我在 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 函数?

4

15 回答 15

70

MSDN

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

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

考虑到正确的方法是:

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

^可以用其他交换操作代替

于 2008-09-16T08:32:00.213 回答
27

请参阅Jon Skeet 的回答- 像这样的二进制操作^并不好,它们经常会产生碰撞哈希!

于 2009-06-22T15:16:58.030 回答
16
public override int GetHashCode()
{
    unchecked
    {
        return (str1 ?? String.Empty).GetHashCode() +
            (str2 ?? String.Empty).GetHashCode();
    }
}

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

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

于 2008-09-16T09:33:42.483 回答
5
  1. 作为一般规则,为类生成哈希码的一种简单方法是异或所有可以参与生成哈希码的数据字段(小心检查其他人指出的空值)。这也满足了 UserInfo("AA", "BB") 和 UserInfo("BB", "AA") 的哈希码相同的(人为?)要求。

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

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

于 2008-09-16T17:29:15.647 回答
4

按照 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 没有什么特别之处可以将它与其他相同大小的素数区分开来。

于 2014-02-06T13:21:00.227 回答
4

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

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

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

于 2014-05-08T14:44:37.003 回答
3
public override int GetHashCode()   
{       
    unchecked      
    {           
        return(str1 != null ? str1.GetHashCode() : 0) ^ (str2 != null ? str2.GetHashCode() : 0);       
    }   
}
于 2008-09-16T09:12:44.467 回答
2

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

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

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

return unchecked(str1.GetHashCode() + str2.GetHashCode());
于 2008-09-16T08:33:56.230 回答
1

试试这个:

(((long)str1.GetHashCode()) + ((long)str2.GetHashCode())).GetHashCode()
于 2008-09-16T08:23:56.613 回答
0

很多可能性。例如

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

于 2008-09-16T08:22:27.893 回答
0

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

于 2008-09-16T08:22:49.423 回答
0

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

返回 ((str1.CompareTo(str2) < 1) ? str1 + str2 : str2 + str1)
    .GetHashCode();
于 2008-09-16T08:27:14.170 回答
0

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();

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

于 2008-09-16T08:31:56.267 回答
0

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

return (str1, str2).GetHashCode();
于 2021-05-10T11:48:17.053 回答
-1

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

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 在异常大比例的实例中不太常见,这是有偏见的。

于 2008-09-16T08:56:48.157 回答