C# 重写GetHashCode并从string属性获取它是否安全?

C# 重写GetHashCode并从string属性获取它是否安全?,c#,.net,gethashcode,C#,.net,Gethashcode,我有一门课: public class Item { public string Name { get; set; } public override int GetHashCode() { return Name.GetHashCode(); } } 重写GetHashCode的目的是希望在字典中只出现一个具有指定名称的对象 但从字符串中获取哈希代码安全吗? 换句话说,两个属性名值不同的对象是否有可能返回相同的哈希代码 但从字符串中获取哈希代

我有一门课:

public class Item
{
    public string Name { get; set; }

    public override int GetHashCode()
    {
        return Name.GetHashCode();
    }
}
重写GetHashCode的目的是希望在字典中只出现一个具有指定名称的对象

但从字符串中获取哈希代码安全吗? 换句话说,两个属性名值不同的对象是否有可能返回相同的哈希代码

但从字符串中获取哈希代码安全吗

是的,它是安全的但是,你正在做的不是。您正在使用可变的
字符串
字段生成哈希代码。假设您插入了一个
项作为给定值的键。然后,有人将
名称
字符串更改为其他内容。您现在无法再在
字典
哈希集
或您使用的任何结构中找到相同的

更重要的是,您应该只依赖不可变类型。我还建议您同时实施
IEquatable

public class Item : IEquatable<Item>
{
    public Item(string name)
    {
        Name = name;
    }

    public string Name { get; }

    public bool Equals(Item other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return string.Equals(Name, other.Name);
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((Item) obj);
    }

    public static bool operator ==(Item left, Item right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(Item left, Item right)
    {
        return !Equals(left, right);
    }

    public override int GetHashCode()
    {
        return (Name != null ? Name.GetHashCode() : 0);
    }
}
公共类项:IEquatable
{
公共项(字符串名称)
{
名称=名称;
}
公共字符串名称{get;}
公共布尔等于(项目其他)
{
if(ReferenceEquals(null,other))返回false;
if(ReferenceEquals(this,other))返回true;
返回字符串.Equals(Name,other.Name);
}
公共覆盖布尔等于(对象对象对象)
{
if(ReferenceEquals(null,obj))返回false;
if(ReferenceEquals(this,obj))返回true;
if(obj.GetType()!=this.GetType())返回false;
返回等于((项目)obj);
}
公共静态布尔运算符==(项目左、项目右)
{
返回等于(左、右);
}
公共静态布尔运算符!=(项目左、项目右)
{
返回!等于(左,右);
}
公共覆盖int GetHashCode()
{
返回(Name!=null?Name.GetHashCode():0);
}
}
两个具有不同属性值的对象是否有可能 Name会返回相同的哈希代码吗


是的,从统计上看,这种事情有可能发生。哈希代码不能保证唯一性。他们致力于单一形式的发行。为什么?因为您的上限是32位的
Int32
。在给定的情况下,最终可能会出现两个不同的字符串,其中包含相同的哈希代码

与使用
GetHashCode
来防止在字典中添加重复项(如前所述,这在您的情况下是有风险的)不同,我建议对您的字典使用一个(自定义的)

如果键是对象,则应创建自己的相等比较器,用于比较
字符串名称
值。如果键是
字符串本身,则可以使用例如


同样在这种情况下,使
字符串
不可变是关键,因为否则您可能会通过更改
名称
使词典无效。您的类有错误,因为您有一个
GetHashCode
覆盖,但没有
等于
覆盖。您也不考虑<代码>名称<代码>为null的情况。

GetHashCode
的规则很简单:

如果
a.Equals(b)
,则必须是
a.GetHashCode()==b.GetHashCode()

更多情况下,如果
!a、 等于(b)
然后
a.GetHashCode()!=b、 GetHashCode()
越好,实际上
的情况越多!a、 等于(b)
然后
a.GetHashCode()%SomeValue!=b、 GetHashCode()%SomeValue
对于任何给定的
SomeValue
(您无法预测它),效果越好,因此我们希望结果中有一个良好的位组合。但重要的是,两个被认为相等的对象必须具有相等的
GetHashCode()
结果

现在情况并非如此,因为您只覆盖了其中一个。然而,以下是合理的:

public class Item
{
  public string Name { get; set; }

  public override int GetHashCode()
  {
      return Name == null ? 0 : Name.GetHashCode();
  }
  public override bool Equals(object obj)
  {
    var asItem = obj as Item;
    return asItem != null && Name == obj.Name;
  }
}
以下更好,因为它允许更快的强类型相等比较:

public class Item : IEquatable<Item>
{
  public string Name { get; set; }

  public override int GetHashCode()
  {
      return Name == null ? 0 : Name.GetHashCode();
  }
  public bool Equals(Item other)
  {
    return other != null && Name == other.Name;
  }
  public override bool Equals(object obj)
  {
    return Equals(obj as Item);
  }
}
现在如果我们使用它,它会起作用:

var dict = Enumerable.Range(0, 1000).Select(i => new SuckyHashCode{Value = i}).ToDictionary(shc => shc);
Console.WriteLine(dict.ContainsKey(new SuckyHashCode{Value = 3})); // True
Console.WriteLine(dict.ContainsKey(new SuckyHashCode{Value = -1})); // False
然而,顾名思义,这并不理想。字典和其他基于散列的集合都有处理冲突的方法,但这些方法意味着我们不再有伟大的接近O(1)的查找,而是随着冲突的百分比越来越大,查找方法就接近O(n)。在上面的例子中,
GetHashCode
在没有实际抛出异常的情况下尽可能糟糕,查找将是O(n),这与将所有项放入无序集合,然后通过查看每个项来查找它们是否匹配相同(事实上,由于管理费用的差异,实际情况更糟)

因此,出于这个原因,我们总是希望尽可能避免冲突。事实上,不仅要避免冲突,还要避免在结果被模化后产生更小的哈希代码(因为这是字典内部发生的事情)

但是在您的情况下,因为
string.GetHashCode()
在避免冲突方面相当出色,而且由于一个字符串是唯一定义相等的东西,因此您的代码在避免冲突方面也相当出色。当然,更具抗冲突性的代码是可能的,但会对代码本身的性能造成损失*和/或超出合理范围

*(尽管我的代码在64位.NET上的大字符串上比
string.GetHashCode()
更快,并且更具抗冲突性,但在32位.NET上或字符串较短时生成这些哈希代码的速度较慢)哈希代码不能保证是唯一的。考虑它返回一个<代码> int <代码>,但是有超过2 ^ 32个可能的字符串。你必须必须只从不可变的值中计算哈希代码——否则你的对象将不能在任何字典或哈希表中正确地工作。
var dict = Enumerable.Range(0, 1000).Select(i => new SuckyHashCode{Value = i}).ToDictionary(shc => shc);
Console.WriteLine(dict.ContainsKey(new SuckyHashCode{Value = 3})); // True
Console.WriteLine(dict.ContainsKey(new SuckyHashCode{Value = -1})); // False