当两个字符串都可以互换时,如何为具有两个字符串的结构实现GetHashCode

时间:2008-09-16 08:17:44

标签: c# hashtable

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

15 个答案:

答案 0 :(得分:65)

MSDN

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

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

考虑到正确的方法是:

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

^可以替换为其他可交换操作

答案 1 :(得分:25)

请参阅Jon Skeet's answer - 像^这样的二进制操作不好,它们通常会产生碰撞哈希!

答案 2 :(得分:15)

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

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

在这个解决方案中并没有完全遵守'尽快'规则,因为在空值的情况下,这对空字符串执行'GetHashCode()'而不是立即返回已知常量,但即使没有明确测量我愿意冒险猜测,除非你期望很多空值,否则差异不会太大而无法担心。

答案 3 :(得分:5)

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

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

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

答案 4 :(得分:4)

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

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

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

答案 5 :(得分:3)

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

答案 6 :(得分:3)

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

答案 7 :(得分:2)

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

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

可以溢出。您可以尝试使用Artem建议的时间,或者您可以在未经检查的关键字中包围该语句:

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

答案 8 :(得分:1)

试试这个:

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

答案 9 :(得分:0)

许多可能性。例如

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

答案 10 :(得分:0)

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

答案 11 :(得分:0)

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

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

答案 12 :(得分:0)

GetHashCode的结果应该是:

  1. 尽快。
  2. 尽可能独特。
  3. 考虑到这些,我会选择这样的事情:

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

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

答案 13 :(得分:0)

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

return (str1, str2).GetHashCode();

答案 14 :(得分:-1)

太复杂了,忘记了空值等。这用于像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在异常大的实例中不太常见,这是有偏见的。

相关问题