我在C#中有一个结构:
public struct UserInfo
{
public string str1
{
get;
set;
}
public string str2
{
get;
set;
}
}
唯一的规则是UserInfo(str1=“AA”,str2=“BB”).Equals(UserInfo(tr1=“BB”,str0=“AA”)
如何覆盖此结构的GetHashCode函数?
我在C#中有一个结构:
public struct UserInfo
{
public string str1
{
get;
set;
}
public string str2
{
get;
set;
}
}
唯一的规则是UserInfo(str1=“AA”,str2=“BB”).Equals(UserInfo(tr1=“BB”,str0=“AA”)
如何覆盖此结构的GetHashCode函数?
MSDN:
哈希函数必须具有以下财产:
- If two objects compare as equal, the
GetHashCode
method for each object must return the same value. However, if two objects do not compare as equal, theGetHashCode
methods for the two object do not have to return different values.- The
GetHashCode
method for an object must consistently return the same hash code as long as there is no modification to the object state that determines the return value of the object sEquals
method. Note that this is true only for the current execution of an application, and that a different hash code can be returned if the application is run again.- For the best performance, a hash function must generate a random distribution for all input.
将其考虑在内的正确方式是:
return str1.GetHashCode() ^ str2.GetHashCode()
^
可以用其他交换运算代替
请参阅Jon Skeet的回答-像^
这样的二进制操作不好,它们经常会生成冲突的哈希!
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,否则差异不会大到令人担忧的程度。
作为一般规则,为类生成哈希代码的一种简单方法是对所有可以参与生成哈希码的数据字段进行异或(如其他人所指出的,要小心检查null)。这也满足了UserInfo(“AA”,“BB”)和UserInfo(”BB“,“AA”)的哈希码相同的(人为?)要求。
如果您能够对类的使用做出假设,那么也许可以改进您的哈希函数。例如,如果str1和str2通常相同,那么XOR可能不是一个好的选择。但是,如果str1和str2表示,比如名字和姓氏,那么XOR可能是一个不错的选择。
Although this is clearly not meant to be a real-world example, it may be worth pointing out that: - This is probably a poor example of use of a struct: A struct should normally have value semantics, which doesn t seem to be the case here. - Using properties with setters to generate a hash code is also asking for trouble.
按照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中没有任何特殊之处将其与其他相同大小的素数区分开来。
一个简单的通用方法是这样做:
return string.Format("{0}/{1}", str1, str2).GetHashCode();
除非您有严格的性能要求,否则这是我能想到的最简单的方法,当我需要复合密钥时,我经常使用这种方法。它可以很好地处理<code>null</code>的情况,并且不会导致(m)任何哈希冲突(一般来说)。如果字符串中需要/,只需选择另一个不需要的分隔符即可。
public override int GetHashCode()
{
unchecked
{
return(str1 != null ? str1.GetHashCode() : 0) ^ (str2 != null ? str2.GetHashCode() : 0);
}
}
啊,是的,正如加里·舒特勒所指出的:
return str1.GetHashCode() + str2.GetHashCode();
可以溢出。你可以试着按照Artem的建议选长,也可以用未选中的关键字将语句括起来:
return unchecked(str1.GetHashCode() + str2.GetHashCode());
试试这个:
(((long)str1.GetHashCode()) + ((long)str2.GetHashCode())).GetHashCode()
从C#7开始,我们可以利用ValueTuple来实现这一点:
return (str1, str2).GetHashCode();
许多可能性。例如
return str1.GetHashCode() ^ str1.GetHashCode()
也许是类似str1.GetHashCode()+str2.GetHashCode的东西?或(str1.GetHashCode()+str2.GetHashCode)/2?这样,无论str1和str2是否交换,它都是一样的。。。。
对它们进行排序,然后将它们连接起来:
return ((str1.CompareTo(str2) < 1) ? str1 + str2 : str2 + str1) .GetHashCode();
GetHashCode的结果应该是:
考虑到这些,我会选择这样的方式:
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();
编辑:忘记空值。代码已固定。
太复杂了,忘记了null等。这是用于像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在异常大比例的实例中不太可能常见。