用C#记录继承Equals的实现

Rid*_*his 5 c# inheritance equality record

我正在尝试创建一个基本记录类型,它将使用Equals()值相等的不同实现,因为它将使用比较集合对象SequenceEqual(),而不是通过引用比较它们。

但是,Equals() 的实现并不像我期望的继承那样工作。

在下面的示例中,我有一个派生类,它有两个不同的列表。在相等性的默认实现下,这些记录是不同的,因为它是通过引用相等性而不是序列相等性来比较列表。

Equals()如果我覆盖基本记录上的默认实现以始终返回true,单元测试将失败,即使代码正在调用RecordBase.Equals(RecordBase obj)

public abstract record RecordBase
{
    public virtual bool Equals(RecordBase obj)
    {
        return true;
    }
}

public record DerivedRecord : RecordBase
{
    public DerivedRecord(ICollection<int> testCollection)
    {
        TestCollection = testCollection;
    }

    public ICollection<int> TestCollection { get; init; }
}

public class RecordTests
{
    [Fact]
    public void Equals_WhenCollectionHasSameValues_ReturnsTrue()
    {
        var recordTest1 = new DerivedRecord(new List<int>() { 1, 2, 3 });
        var recordTest2 = new DerivedRecord(new List<int>() { 1, 2, 3 });

        Assert.True(recordTest1.Equals(recordTest2));
    }
}
Run Code Online (Sandbox Code Playgroud)

有趣的是,如果我更改实现,以便Equals()在 上实现DerivedRecord,而不是在 上实现RecordBase,则单元测试将通过。

public record DerivedRecord : RecordBase
{
    public DerivedRecord(ICollection<int> testCollection)
    {
        TestCollection = testCollection;
    }

    public virtual bool Equals(DerivedRecord obj)
    {
        return true;
    }

    public ICollection<int> TestCollection { get; init; }
}
Run Code Online (Sandbox Code Playgroud)

此外,这个问题特定于记录:如果我将第一个示例的实现更改为使用类,则两个实例将评估为相等并且单元测试将通过。

public abstract class RecordBase
{
    public virtual bool Equals(RecordBase obj)
    {
        return true;
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,尝试覆盖Equals()记录的默认实现是有问题的,其中派生记录不会继承基本记录的实现。

是否有一个原因?直观上,派生记录似乎应该能够继承基本记录的基于值的相等实现。但是,我一直在阅读C# 9.0 记录规范,并且不确定是否有一个综合实现可以防止这种情况,或者是否可以使用记录来实现这一点。

ang*_*son 5

不幸的是,记录的行为并不如您所期望的那样。

当您声明记录时,您可以免费获得相等检查运算符和方法。

您的基类只返回true,但是当您将派生记录声明为 a 时record,您还会在其中获得一个相等性检查方法,如下所示:

public virtual bool Equals(DerivedRecord other)
{
    return (object)this == other || (base.Equals(other) &&
        EqualityComparer<ICollection<int>>.Default.Equals(
            this.TestCollection,
            other.TestCollection));
}
Run Code Online (Sandbox Code Playgroud)

由于EqualityComparer<ICollection<int>>.Default.Equals进行了简单的引用比较,因此两个列表虽然具有相同的内容,但不被视为相等,因此您会返回false.

如果将类型更改为与class相反record,则不会添加编译器生成的Equals方法和相关运算符,并且您将保留基类中返回的类型true

但对于record类型,您将为每个继承级别和类型获取该方法。这也是为什么直接在派生记录类型上实现它也可以根据您的预期“工作”,因为这样编译器就不会为您生成记录类型。