C#重写GetHashCode()

本文探讨了在C#中重写GetHashCode()的重要性,特别是在使用对象作为散列表键时。文章介绍了重写GetHashCode()的三个必须原则,以及四个性能优化点。还提供了一个Coordinate类的示例,展示了如何结合对象属性进行散列码的生成,以避免冲突并确保性能。此外,提到了安全性考虑,防止攻击者通过伪造具有特定散列码的对象来影响散列表性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

自学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没有对散列码缓存。由于执行散列码计算时,每个字段都是只读的,所以值不会改变。

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


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值