记录类型:重写 EqualityContract 会中断 equality/hashcode 匹配

Record types: overriding EqualityContract breaks the equality/hashcode match

新引入的记录类型允许覆盖 EqualityContract 类型,这使得可以创建一种情况,当两个对象相等但具有不同的哈希码时,反对 guidelines 覆盖 GetHashCode

If you override the GetHashCode method, you should also override Equals, and vice versa. If your overridden Equals method returns true when two objects are tested for equality, your overridden GetHashCode method must return the same value for the two objects.

    public record Base(string Foo);

    public record Child(string Foo, string Bar) : Base(Foo)
    {
        protected override Type EqualityContract => typeof(Base);
    }

    var b = new Base("Foo");
    var c = new Child("Foo", "Bar");
    Console.WriteLine(b == c); // True
    Console.WriteLine(b.GetHashCode() == c.GetHashCode()); // False

Child 中删除额外的 属性 会使 GetHashCodeEquals“匹配”。

显然这可以通过覆盖 GetHashCode 来解决,但我想知道为什么覆盖 EqualityContract 不会自动导致 GetHashCode return 值“匹配”Equals 实施?或者除了手动 GetHashCode 覆盖之外,还有其他方法可以处理这个问题。

如您在 equality members part of records proposal

中所见

GetHashCode() returns an int result of a deterministic function combining the following values:

  • For each instance field fieldN in the record type that is not inherited, the value of System.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN) where TN is the field type, and

  • If there is a base record type, the value of base.GetHashCode(); otherwise the value of System.Collections.Generic.EqualityComparer<System.Type>.Default.GetHashCode(EqualityContract).

因此,在您的示例中,GetHashCode 对于 Base 将是

public override int GetHashCode()
{
    return EqualityComparer<Type>.Default.GetHashCode(EqualityContract) + EqualityComparer<string>.Default.GetHashCode(Foo);
}

而对于 Child

public override int GetHashCode()
{
    return base.GetHashCode() + EqualityComparer<string>.Default.GetHashCode(Bar);
}

对于继承的记录,EqualityContract 不用于哈希码计算。如果删除了额外的 属性 Bar,则使用 Base 中的值,您将获得哈希值相等。因此,需要覆盖 GetHashCode

使用 sharplab.io

也可以观察到这种行为