分析问题
GetHashCode方法的功能是根据当前对象返回一个散列值,可以用来在数据结构算法或哈希算法中使用。GetHashCode算法的根本要求是同一个类型对象调用GetHashCode必须返回相同的值,更进一步的,相等的对象必须返回相同的值,这样才能使得这个散列值可以用在容器算法中。
GetHashCode方法在Object类型有一个默认实现,基本的思想是使用一个内部对象索引成员来生成散列值。对象索引永远不会重复,也不会被改变,但同一对象只拥有一个索引,这保证了GetHashCode最基本的需求:同一对象返回相同的值。事实上,对于所有值类型来说GetHashCode的默认实现是永远不满足这一引申需求的。
说明
对象的相等是指调用Equals方法返回true。这也是之前代码编译会产生一个警告。Equals方法被重写了,编译器无法确定原先被认为相等的、将产生相同HashCode的对象是否在新的比较方法中返回true。
在自定义的类型需要作为哈希表的键值使用时,就需要重写GetHashCode方法,尤其是当新类型是值类型或者新类型重写了Equals方法时。在本书前面章节已经提到,重写GetHashCode有三个重要的原则:
1、相等的对象调用GetHashCode返回相等的值。
2、同一对象任何时候调用GetHashCode方法返回同一值。
3、对于所有对象输出值尽量散列分布。
在Object中实现GetHashCode算法,不能保证上述3条的第一条原则,这是因为在原始基类中,无法获得足够的具体类型的信息,并且不能保证Equals方法不被重写。以下代码是一个重写GetHashCode的示例。
using System; namespace Test { class OverrideGetHashCode { public int _MyInt; public readonly string _MyString; public override bool Equals(object obj) { if (obj == null) { return false; } if (object.ReferenceEquals(this, obj)) { return true; } if (this.GetType()!=obj.GetType()) { return false; } OverrideGetHashCode current = obj as OverrideGetHashCode; if (_MyInt==current._MyInt&&_MyString==current._MyString) { return true; } return false; } public OverrideGetHashCode(int i, string s) { _MyInt = i; _MyString = s; } //重写GetHashCode方法 public override int GetHashCode() { //依靠只读成员的HashCode return _MyString.GetHashCode(); } static void Main() { //验证相等的对象返回相等的HashCode int i = 10; string s = "我是字符串"; OverrideGetHashCode o1 = new OverrideGetHashCode(i, s); OverrideGetHashCode o2 = new OverrideGetHashCode(i, s); Console.WriteLine("对象是否相等:{0}", (o1.Equals(o2)).ToString()); Console.WriteLine("相等对象HashCode是否相等:{0}", (o1.GetHashCode() == o2.GetHashCode()).ToString()); //验证同一对象返回相等的HashCode int code = o1.GetHashCode(); o1._MyInt = 12; Console.WriteLine("同一对象HashCode是否相等:{0}", (o1.GetHashCode() == code).ToString()); Console.Read(); } } }
在以上代码中,类型OverrideGetHashCode重写了Equals方法,实现了内容比较,并且重写了GetHashCode方法,在Main方法中,代码运用重写GetHashCode的前两条原则对算法进行了验证,而对于HashCode的散列分布情况,以上代码的算法并不理想,因为具有不同_MyInt值但具有相同_MyString值的对象将拥有相同的HashCode。在以上代码中,有几点需要读者特别留意:
1、新的GetHashCode算法依赖的是只读成员的GetHashCode算法,这是为了确保第二条原则。
2、算法不能确保不同对象返回不同的HashCode,这并不完全违反GetHashCode的原则,但却使得该类型在应用在散列表中时拥有较差的性能。
3、并不是所有类型都会有只读成员,所以通常情况下重写GetHashCode会非常困难。无论如何,确保算法的正确性比确保其效率更为重要。
答案
Object中GetHashCode的算法保证了同一对象返回同一HashCode,而不同对象则返回不同的HashCode,但对于值类型等视内容相等的对象为相等对象的类型时,默认的GetHashCode算法并不正确。
重写GetHashCode必须保证同一对象无论何时都返回同一HashCode值,而相等的对象也必须返回相同的值。并且在此基础上,保证HashCode尽量随机地散列分布。
如何重写GetHashCode方法