C#重写GetHashCode()

自学C#本质论

重写重写重写重写重重写重重写重写重写重写重写重写重写重重--------------------------------------------------------------------------------------------------

当你想要重写Equals()时,也应该同时重写GetHashCode()。若忘记此操作,编译器显示警告:在将类作为散列表集合(比如System.Collections.Hashtable和System.Collections.Generic,Dictionary)的键(key)使用时,最好也将GetHash()重写

散列码即Hashcode,作用是生成一个与对象值对应的数字,从而高效地平衡一个散列表。

一个良好的GetHashCode实现的原则如下::::(性能指为了增加性能而需要采取的措施;安全性是指为了保障安全性而产生的措施)

必须1.             相等的对象必然有相等的散列码(若a.Equals(b),则a.GetHashCode()==b.GetHashCode())

必须2.             针对一个特定的对象,在这个对象的生存期内,GetHashCode()始终应该返回相同的值,即使对象的数据发生了改变。许多时候应该缓存方法的返回值,从而确保返回相同的值

必须3             .GetHash()不应该引发任何异常,必须总是成功的返回一个值

性能1.        散列码尽可能保持唯一,然而,由于散列码返回一个int值,只要某个对象包含的值比一个int容纳的多,散列码肯定存在重复。如long取值范围大于int,所以假如每个int值都只能标识一个不同的long值那么肯定会剩下大量的long值无法标识。

性能2.        可能的散列码值应当在int范围内平均分布。如:创建散列码时,假如没有考虑到字符串在拉丁语言的分布主要集中在初始的128个ASCII字符上,就会造成字符串值分布非常不均匀,所以不是可靠地GetHashCode算法

性能3         GetHashCode的性能应该优化。GetHashCode通常在Equals实现中用于短路一次完整的相等性比较(假如散列码不同,当然就没有必要进行完整的相等性比较)。所以当类型欧威字典集合的键类型使用时,会频繁调用这个方法

性能4          两对象的细小差异会造成散列码值的巨大差异。理想情况:1bit的差异造成散列码差异为16bits。这有助于不管散列表如何为散列码值装桶,也能保持良好的平衡性。

安全性1.   攻击者难以伪造一个具有特定散列码的对象。攻击的手法是像散列码中填写大量都散列成同一个值的数据。散列表的实现就会变成O(n),而不是O(1),造成可能的DOS(拒绝服务)攻击

例题实现GetHashCode()

    public class Longitude{

    }
    public class Latitude{

    }
    public struct Coordinate
    {
        private readonly Latitude _Latitude;
        private readonly Longitude _Longitude;
        public Longitude Longitude
        {
            get { return _Longitude; }
        }
        public Latitude Latitude
        {
            get { return _Latitude; }
        }
        public Coordinate(Longitude longitude,Latitude latitude)
        {
            _Longitude = longitude;
            _Latitude = latitude;
        }
        public override string ToString()
        {
            return string.Format("{0} {1}", Longitude, Latitude);
            //return base.ToString();
        }
        public override int GetHashCode()
        {
            int hashCode = Longitude.GetHashCode();
            if (Longitude.GetHashCode() != Latitude.GetHashCode())
                hashCode ^= Latitude.GetHashCode();
            return hashCode;
            //return base.GetHashCode();
        }

    }
通常采取的做法是来自相应类型的散列码应用XOR即异或运算符,并确保XOR的操作数不相近或相等吗,否则结果全为0..

操作数相近的情况下,考虑移位或加法运算。其他备选运算符And或OR有限制。。多次应用And会逐渐变为0,多次用Or会逐渐都变为1.

为了更细致的控制,可使用移位运算符来分解一个比int大的类型。如假定long value,可实现为: int GetHashCode() { return ((int) value ^(int)(value>>32); }

!!注意  假如基类不是object,则应在XOR赋值中包含base.GetHashCode()。

最后,coordinate没有对散列码缓存。由于执行散列码计算时,每个字段都是只读的,所以值不会改变。

值可能变化或者缓存能优化性能,则需要对散列码缓存。


你可能感兴趣的:(编程)