分析问题
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尽量随机地散列分布。