您的课程有问题,因为您有 GetHashCode 覆盖,但没有 Equals 覆盖。您也不会考虑Name 为空的情况。
GetHashCode 的规则很简单:
如果是a.Equals(b),那么一定是a.GetHashCode() == b.GetHashCode()。
如果!a.Equals(b) 然后a.GetHashCode() != b.GetHashCode() 的情况越多越好,实际上!a.Equals(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);
}
}
换句话说,两个具有不同属性名称值的对象是否有可能返回相同的哈希码?
是的,这可能会发生,但不会经常发生,所以没关系。 Dictionary 和 HashSet 这样的基于哈希的集合可以处理一些冲突;即使哈希码都不同,确实会发生冲突,因为它们被模降低到更小的索引。只有这种情况经常发生才会影响性能。
另一个危险是您将使用可变值作为键。有一个神话说你不应该对哈希码使用可变值,这是不正确的;如果一个可变对象有一个可变属性会影响它被视为相等的对象,那么它必须导致哈希码发生变化。
真正的危险是改变一个对象,它是哈希集合的关键。如果您基于Name 定义相等,并且您有这样一个对象作为字典的键,那么您不得在将Name 用作这样的键时更改它。确保这一点的最简单方法是让Name 是不可变的,所以如果可能的话,这绝对是一个好主意。但是,如果不可能,则在允许更改 Name 时需要小心。
来自评论:
那么,即使哈希码发生冲突,当 Equals 会返回 false(因为名称不同)时,Dictionary 会正确处理吗?
是的,它会处理它,但它并不理想。我们可以用这样的类来测试它:
public class SuckyHashCode : IEquatable<SuckyHashCode>
{
public int Value { get; set; }
public bool Equals(SuckyHashCode other)
{
return other != null && other.Value == Value;
}
public override bool Equals(object obj)
{
return Equals(obj as SuckyHashCode);
}
public override int GetHashCode()
{
return 0;
}
}
现在如果我们使用它,它就可以工作了:
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() 在避免冲突方面相当出色,并且因为一个字符串是唯一定义相等性的东西,所以您的代码反过来在避免冲突方面也相当不错。更多的抗冲突代码当然是可能的,但会以代码本身的性能为代价*和/或工作量超出合理范围。
*(虽然我的代码见https://www.nuget.org/packages/SpookilySharp/,它在 64 位 .NET 上的大字符串上比 string.GetHashCode() 更快,并且更耐碰撞,尽管在 32 位上生成这些哈希码的速度较慢。 NET 或当字符串很短时)。