当两个字符串都是可互换的时候,如何实现带有两个字符串的结构的 GetHashCode

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

44487 次浏览

许多可能性。

返回 str1.GetHashCode () ^ str1.GetHashCode ()

比如 str1.GetHashCode () + str2.GetHashCode () ?或者(str1.GetHashCode () + str2.GetHashCode ())/2?这样,不管 str1和 str2是否交换,它都是一样的... ..。

试试这个:

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

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

return ((str1.CompareTo(str2) < 1) ? str1 + str2 : str2 + str1)
.GetHashCode();

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

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

MSDN :

散列函数必须具有以下属性:

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

正确的做法是:

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

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

啊,是的,正如加里•沙特勒(Gary Shutler)所指出的:

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

会溢出来。你可以尝试按照 Artem 的建议进行施法,或者你也可以用 uncheck 关键字将语句包围起来:

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

太复杂,而且会忘记空值等等。这是用于类似桶的东西,所以你可以摆脱这样的东西

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不太可能在异常大比例的实例中是常见的。

public override int GetHashCode()
{
unchecked
{
return(str1 != null ? str1.GetHashCode() : 0) ^ (str2 != null ? str2.GetHashCode() : 0);
}
}
public override int GetHashCode()
{
unchecked
{
return (str1 ?? String.Empty).GetHashCode() +
(str2 ?? String.Empty).GetHashCode();
}
}

使用’+’操作符可能比使用’^’要好,因为尽管您显式地希望(‘ AA’,‘ BB’)和(‘ BB’,‘ AA’)显式地相同,但您可能不希望(‘ AA’,‘ AA’)和(‘ BB’,‘ BB’)相同(或者所有相同的对)。

在这个解决方案中并没有完全遵循“尽可能快”的规则,因为在 null 的情况下,它在空字符串上执行“ GetHashCode ()”,而不是立即返回一个已知的常量,但是即使没有明确的测量,我也愿意大胆地猜测,除非你期望有很多 null,否则这种差别不足以让你担心。

  1. 作为一般规则,为类生成哈希代码的一种简单方法是异或所有可以参与生成哈希代码的数据字段(正如其他人指出的那样,要小心检查 null)。这也符合(人工?)要求 UserInfo (“ AA”,“ BB”)和 UserInfo (“ BB”,“ AA”)的哈希代码相同。

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

虽然这显然不是一个现实世界的例子,但可能值得指出的是: 这可能是使用 struct 的一个不好的例子: struct 通常应该具有值语义,这里似乎没有这种情况。 - 使用带设置器的属性生成哈希代码也是自找麻烦。

看到 Jon Skeet 的回答-像 ^这样的二进制操作都不好,它们经常会产生碰撞散列!

按照 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是一个大小足够的素数,可以导致 result 变量溢出,并在某种程度上混合散列的位,从而提供更好的散列代码分布。除此之外,在397中没有什么特别之处可以将它与其他同等数量的质数区分开来。

一个简单的 将军方法是这样做:

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

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

从 C # 7开始,我们可以利用 ValueTuple 来实现这一点:

return (str1, str2).GetHashCode();