当两个string可以互换时,如何为两个string的结构实现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函数?

MSDN :

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

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

考虑到正确的方法是:

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

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

看Jon Skeet的回答 – 像^这样的二元运算不好,他们经常会产生碰撞哈希!

 public override int GetHashCode() { unchecked { return (str1 ?? String.Empty).GetHashCode() + (str2 ?? String.Empty).GetHashCode(); } } 

使用'+'运算符可能比使用'^'更好,因为尽pipe你明确地想要('AA','BB')和('BB','AA')明确地相同,你可能不希望'AA','AA')和('BB','BB')是相同的(或者所有相同的对)。

在这个解决scheme中,“尽可能快”的规则并没有完全遵守,因为在空值的情况下,这对空string执行“GetHashCode()”,而不是立即返回已知的常量,但是即使没有明确地测量,我也愿意冒险猜测,除非你期望有很多的空值,否则差别不会太大。

  1. 作为一般规则,为类生成哈希码的一种简单方法是对可参与生成哈希码的所有数据字段进行异或(注意检查其他人指出的空)。 这也符合用户信息(“AA”,“BB”)和用户信息(“BB”,“AA”)的哈希码相同的(人为的)要求。

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

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

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

按照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是引起结果variables溢出的足够大小的主要元素,并且在某种程度上混合散列的位,从而提供散列码的更好的分布。 否则,397中没有什么特别的区别于同等数量的其他素数。

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

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

可以溢出。 您可以尝试投射,只要Artembuild议,或者您可以围绕未经检查的关键字声明:

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

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

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

除非你有严格的性能要求,这是我能想到的最简单的方法,当我需要一个复合键时,我经常使用这种方法。 它处理null情况就好了,不会造成(m)任何散列冲突(一般)。 如果您希望在string中使用“/”,只需select另一个您不希望的分隔符。

试试这个:

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

许多可能性。 例如

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

也许像str1.GetHashCode()+ str2.GetHashCode()? 或(str1.GetHashCode()+ str2.GetHashCode())/ 2? 不pipestr1和str2是否被交换,这种方式都是一样的。

对它们进行sorting,然后连接它们:

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

编辑:忘记了空值。 代码固定。

太复杂了,忘记了空值等等。这个用于像bucketing这样的东西,所以你可以用类似的东西逃脱

 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在不寻常的大部分情况下可能不常见,这是有偏见的。