为什么不 List<T>.GetHashCode 和 ObservableCollection<T>.GetHashCode 评估他们的项目?

why don't List<T>.GetHashCode and ObservableCollection<T>.GetHashCode evaluate their items?

我认为这些集合的 GetHashCode 函数不将其哈希码基于列表中的项目很奇怪。

我需要它来工作以提供脏检查(您有未保存的数据)。 我写了一个覆盖 GetHashCode 方法的包装 class,但我发现这不是默认实现很奇怪。

我猜这是性能优化?

class Program
{
    static void Main(string[] args)
    {
        var x = new ObservableCollection<test>();
        int hash = x.GetHashCode();
        x.Add(new test("name"));
        int hash2 = x.GetHashCode();

        var z = new List<test>();
        int hash3 = z.GetHashCode();
        z.Add(new test("tets"));
        int hash4 = z.GetHashCode();

        var my = new CustomObservableCollection<test>();
        int hash5 = my.GetHashCode();
        var test = new test("name");
        my.Add(test);
        int hash6 = my.GetHashCode();
        test.Name = "name2";
        int hash7 = my.GetHashCode();
    }
}

public class test
{
    public test(string name)
    {
        Name = name;
    }

    public string Name { get; set; }

    public override bool Equals(object obj)
    {
        if (obj is test)
        {
            var o = (test) obj;
            return o.Name == this.Name;
        }
        return base.Equals(obj);
    }

    public override int GetHashCode()
    {
        return Name.GetHashCode();
    }
}

public class CustomObservableCollection<T> : ObservableCollection<T>
{
    public override int GetHashCode()
    {
        int collectionHash = base.GetHashCode();

        foreach (var item in Items)
        {
            var itemHash = item.GetHashCode();
            if (int.MaxValue - itemHash > collectionHash)
            {
                collectionHash = collectionHash * -1;
            }
            collectionHash += itemHash;
        }
        return collectionHash;
    }
}

GetHashCode 的关键是以轻量级的方式反映 Equals() 逻辑。

List<T>.Equals()继承了Object.Equals(),而Object.Equals()通过引用比较相等性,所以列表不基于它的项,而是列表本身

列表的 Equals/GetHashCode 检查引用相等性,而不是内容相等性。这背后的原因是,列表都是 mutable 和引用(不是结构)对象。因此每次更改内容时,哈希码都会更改。

散列码的常见用例是散列 tables(例如 Dictionary<K,V> 或 HashSet),它们在首次插入 [=18= 时根据散列对项目进行排序].如果一个对象的散列已经在 table 变化中,它可能不再被找到,这会导致不稳定的行为。

如果这样做,它会破坏一些 guidelines for implementing GetHashCode。即:

the integer returned by GetHashCode should never change

既然列表的内容可以改变,那么它的哈希码也会改变。

the implementation of GetHashCode must be extremely fast

根据列表的大小,您可能会减慢其哈希码的计算速度。

此外,我认为您不应该使用对象的哈希码来检查数据是否脏。 The probability of collision is higher than you think.

有一些行为类似于 List<T> 并且通常可以与它互换使用的类型会很有帮助,但是 GetHashCodeEquals 方法可以定义等价性根据身份序列,或封装在其中的项目的 EqualsGetHashCode 行为。然而,要使此类方法有效运行,需要 class 包含代码来缓存其哈希值,但每当修改集合时都使缓存的哈希值无效或更新(在列表修改时修改列表是不合法的)被存储为字典键,但删除列表、修改它和重新添加它应该是合法的,并且非常希望避免这种修改需要重新散列列表的全部内容)。让普通列表以减慢对永远不会被散列的列表的操作为代价来支持这种行为的努力被认为是不值得的;根据他们应该在其成员中寻找或应该向外界公开的等价类型来定义多种类型的列表、多种类型的字典等也不被认为是值得的。