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