c#如何计算对象的哈希码?
这个问题来自元组的讨论。
我开始考虑元组应该具有的哈希码。
如果我们将KeyValuePair类作为元组接受怎么办?它不会覆盖GetHashCode()方法,因此它可能不会意识到其"子级"的哈希码...因此,运行时将调用Object.GetHashCode(),而后者不知道真实的对象结构。
然后,由于过载的GetHashCode()和Equals(),我们可以使两个引用类型的实例实际上是Equal。并在元组中将它们用作"子代"来"欺骗"字典。
但这是行不通的!运行时以某种方式弄清楚了元组的结构,并调用了我们类的重载GetHashCode!
它是如何工作的? Object.GetHashCode()所做的分析是什么?
当我们使用一些复杂的密钥时,在某些恶劣的情况下会影响性能吗? (可能是不可能的情况,但仍然)
以下面的代码为例:
namespace csharp_tricks
{
class Program
{
class MyClass
{
int keyValue;
int someInfo;
public MyClass(int key, int info)
{
keyValue = key;
someInfo = info;
}
public override bool Equals(object obj)
{
MyClass other = obj as MyClass;
if (other == null) return false;
return keyValue.Equals(other.keyValue);
}
public override int GetHashCode()
{
return keyValue.GetHashCode();
}
}
static void Main(string[] args)
{
Dictionary<object, object> dict = new Dictionary<object, object>();
dict.Add(new KeyValuePair<MyClass,object>(new MyClass(1, 1), 1), 1);
//here we get the exception -- an item with the same key was already added
//but how did it figure out the hash code?
dict.Add(new KeyValuePair<MyClass,object>(new MyClass(1, 2), 1), 1);
return;
}
}
}
更新我认为我已经找到了对此的解释,如下所述。它的主要结果是:
- 注意键及其哈希码:-)
- 对于复杂的字典键,我们必须正确重写Equals()和GetHashCode()。
解决方案
我没有这本书的参考文献了,我只需要进行确认就可以找到它,但是我认为默认的基础哈希只是将对象的所有成员哈希在一起。由于CLR的工作方式,它们可以访问它们,因此我们无法像他们一样写。
这完全是出于我简短阅读过的内容的记忆,因此请随心所欲。
编辑:这本书来自MS Press的InsideC。带有锯片的盖子。作者花费了大量时间来解释如何在CLR中实现事物,如何将语言翻译成MSIL等。等等如果我们能找到这本书,那就不错了。
编辑:形成看起来像的链接
Object.GetHashCode() uses an internal field in the System.Object class to generate the hash value. Each object created is assigned a unique object key, stored as an integer,when it is created. These keys start at 1 and increment every time a new object of any type gets created.
嗯,如果我希望将对象用作哈希键,我想我需要编写一些自己的哈希码。
不要在可变的类上覆盖GetHashcode()和Equals(),而仅在不可变的类或者结构上覆盖它,否则,如果修改用作键的对象,则哈希表将无法正常工作(我们将无法修改键对象后检索与键关联的值)
同样,哈希表不使用哈希码来标识对象,它们使用自己的键对象作为标识符,也不需要所有用于在哈希表中添加条目的键都返回不同的哈希码,但是建议这样做,否则会降低性能遭受很大的痛苦。
查看Brad Abrams的这篇文章,以及Brian Grunkemeyer的评论,以获取有关object.GetHashCode的工作原理的更多信息。另外,请查看Ayande博客文章的第一条评论。我不知道框架的当前版本是否仍然遵循这些规则,或者是否像Brad暗示的那样实际更改了框架。
so probably it won't be aware of the hash codes of it's "children".
示例似乎证明了:-)键MyClass和值1的哈希码对于两个KeyValuePair都是相同的。 KeyValuePair实现必须同时使用其" Key"和" Value"作为其自己的哈希码
向上移动,字典类需要唯一的键。它使用每个键提供的哈希码来解决问题。请记住,运行时不是在调用Object.GetHashCode(),而是在调用我们提供的实例提供的GetHashCode()实现。
考虑一个更复杂的情况:
public class HappyClass
{
enum TheUnit
{
Points,
Picas,
Inches
}
class MyDistanceClass
{
int distance;
TheUnit units;
public MyDistanceClass(int theDistance, TheUnit unit)
{
distance = theDistance;
units = unit;
}
public static int ConvertDistance(int oldDistance, TheUnit oldUnit, TheUnit newUnit)
{
// insert real unit conversion code here :-)
return oldDistance * 100;
}
/// <summary>
/// Figure out if we are equal distance, converting into the same units of measurement if we have to
/// </summary>
/// <param name="obj">the other guy</param>
/// <returns>true if we are the same distance</returns>
public override bool Equals(object obj)
{
MyDistanceClass other = obj as MyDistanceClass;
if (other == null) return false;
if (other.units != this.units)
{
int newDistance = MyDistanceClass.ConvertDistance(other.distance, other.units, this.units);
return distance.Equals(newDistance);
}
else
{
return distance.Equals(other.distance);
}
}
public override int GetHashCode()
{
// even if the distance is equal in spite of the different units, the objects are not
return distance.GetHashCode() * units.GetHashCode();
}
}
static void Main(string[] args)
{
// these are the same distance... 72 points = 1 inch
MyDistanceClass distPoint = new MyDistanceClass(72, TheUnit.Points);
MyDistanceClass distInch = new MyDistanceClass(1, TheUnit.Inch);
Debug.Assert(distPoint.Equals(distInch), "these should be true!");
Debug.Assert(distPoint.GetHashCode() != distInch.GetHashCode(), "But yet they are fundimentally different values");
Dictionary<object, object> dict = new Dictionary<object, object>();
dict.Add(new KeyValuePair<MyDistanceClass, object>(distPoint, 1), 1);
//this should not barf
dict.Add(new KeyValuePair<MyDistanceClass, object>(distInch, 1), 1);
return;
}
}
基本上,在我的示例中,我们希望两个距离相同的对象为Equals返回" true",但返回不同的哈希码。
看来我现在有一个线索。
我以为KeyValuePair是引用类型,但不是,它是结构。因此,它使用ValueType.GetHashCode()方法。 MSDN为此表示:"派生类型的一个或者多个字段用于计算返回值"。
如果我们将真正的引用类型用作"元组提供者",则我们会欺骗字典(或者我们自己)。
using System.Collections.Generic;
namespace csharp_tricks
{
class Program
{
class MyClass
{
int keyValue;
int someInfo;
public MyClass(int key, int info)
{
keyValue = key;
someInfo = info;
}
public override bool Equals(object obj)
{
MyClass other = obj as MyClass;
if (other == null) return false;
return keyValue.Equals(other.keyValue);
}
public override int GetHashCode()
{
return keyValue.GetHashCode();
}
}
class Pair<T, R>
{
public T First { get; set; }
public R Second { get; set; }
}
static void Main(string[] args)
{
var dict = new Dictionary<Pair<int, MyClass>, object>();
dict.Add(new Pair<int, MyClass>() { First = 1, Second = new MyClass(1, 2) }, 1);
//this is a pair of the same values as previous! but... no exception this time...
dict.Add(new Pair<int, MyClass>() { First = 1, Second = new MyClass(1, 3) }, 1);
return;
}
}
}
这是四元组的正确哈希和相等实现(内部包含4个元组组件)。此代码可确保在HashSets和字典中正确使用此特定元组。
此处提供有关该主题的更多信息(包括源代码)。
注意使用unchecked关键字(以避免溢出),如果obj为null(根据基本方法的要求),则抛出NullReferenceException。
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
throw new NullReferenceException("obj is null");
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != typeof (Quad<T1, T2, T3, T4>)) return false;
return Equals((Quad<T1, T2, T3, T4>) obj);
}
public bool Equals(Quad<T1, T2, T3, T4> obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
return Equals(obj.Item1, Item1)
&& Equals(obj.Item2, Item2)
&& Equals(obj.Item3, Item3)
&& Equals(obj.Item4, Item4);
}
public override int GetHashCode()
{
unchecked
{
int result = Item1.GetHashCode();
result = (result*397) ^ Item2.GetHashCode();
result = (result*397) ^ Item3.GetHashCode();
result = (result*397) ^ Item4.GetHashCode();
return result;
}
}
public static bool operator ==(Quad<T1, T2, T3, T4> left, Quad<T1, T2, T3, T4> right)
{
return Equals(left, right);
}
public static bool operator !=(Quad<T1, T2, T3, T4> left, Quad<T1, T2, T3, T4> right)
{
return !Equals(left, right);
}

