分析问题

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