blog

不要使用 struct 默认的 GetHashCode

经常有人在 Dictionary<TKey,TValue>HashSet<T> 中误用没有自定义 GetHashCodestruct 做 key,出现性能问题。这是默认的 ValueTypeGetHashCode 实现存在的问题造成的。

GetHashCode 有个原则是:对于所有 Equals 的对象,它们的 GetHashCode 应该返回相等的值。但反之则没有要求,也就是并没有要求不一样的对象一定要返回不同的 hashcode,这在物理上是不可能做到的,因为 int 的范围相当有限。

CLR 中对于 structGetHashCode 的默认实现为,先检查 struct 的所有字段,分两种情况处理:

struct Test { public decimal value; }

static void Main() {
    var t1 = new Test() { value = 1.0m };
    var t2 = new Test() { value = 1.00m };
    if (t1.GetHashCode() != t2.GetHashCode())
        Console.WriteLine("fuck!");
}
struct Test
{
    public int i;
    public string s; //不管 s 的内容是什么都影响不了 hashcode
}

结论

由于 struct 的 layout 经常不可控(调换下字段的顺序就可能造成影响),以及情况 2 的存在,使用默认的 GetHashCode 实现是很不明智的。对于自定义的 struct,绝不要使用默认实现,应该自定义靠谱的 GetHashCode。可以参考 ValueTuple<T1, T2> 的实现,使用 HashCode.Combine<T1, T2, T...>(value1, value2, ...) 来生成自定义的 hashcode

番外

对于 Enum,调用它的 Equals 会导致装箱(之前版本的 clr 调用 GetHashCode 也会,目前版本的改过来了)。为了避免这种情况,请调用 EqualityComparer<Enum>.Default.Equals(enum1, enum2)