使用 C# 记录继承 Equals 的实现
Inheriting implementation of Equals with C# record
我正在尝试创建一个基本记录类型,它将使用 Equals()
的不同实现来实现值相等,因为它将使用 SequenceEqual()
来比较集合对象,而不是通过引用来比较它们.
但是,Equals() 的实现并没有像我期望的继承那样工作。
在下面的示例中,我得到了一个派生的 class,它有两个不同的列表。在相等性的默认实现下,这些记录是不同的,因为它是通过引用相等性而不是序列相等性来比较列表。
如果我将基本记录上 Equals()
的默认实现覆盖为始终 return 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));
}
}
有趣的是,如果我更改实现,使 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; }
}
此外,此问题特定于记录:如果我将第一个示例的实现更改为使用 classes,则两个实例将评估为相等并且单元测试将通过。
public abstract class RecordBase
{
public virtual bool Equals(RecordBase obj)
{
return true;
}
}
因此,在尝试使用记录覆盖 Equals()
的默认实现时,派生记录不会继承基本记录的实现。
这有什么原因吗?直觉上,派生记录似乎应该能够继承基记录对基于值的相等性的实现。但是,我一直在通读 C# 9.0 specification for records,我不确定是否有一个合成的实现可以防止这种情况发生,或者这是否有可能使用记录。
不幸的是,记录的行为方式与您期望的不同。
当您声明一条记录时,您将免费获得相等性检查运算符和方法。
你的基础 class 只是 returns true
,但是当你将派生记录声明为 record
时,你也会在那里得到一个相等性检查方法,看起来像这样:
public virtual bool Equals(DerivedRecord other)
{
return (object)this == other || (base.Equals(other) &&
EqualityComparer<ICollection<int>>.Default.Equals(
this.TestCollection,
other.TestCollection));
}
由于EqualityComparer<ICollection<int>>.Default.Equals
做了一个简单的引用比较,所以这两个列表虽然内容相同,但不认为是相等的,因此你得到false
。
如果您将类型更改为仅 class
而不是 record
,则不会添加编译器生成的 Equals
方法和相关运算符,您只剩下一个从基数 class 开始,即 returns true
.
但是对于 record
类型,您将获得针对每个继承级别和类型的方法。这也是为什么直接在派生记录类型上实现它也能根据您的预期“工作”,从那时起编译器就不会为您生成一个。
我正在尝试创建一个基本记录类型,它将使用 Equals()
的不同实现来实现值相等,因为它将使用 SequenceEqual()
来比较集合对象,而不是通过引用来比较它们.
但是,Equals() 的实现并没有像我期望的继承那样工作。
在下面的示例中,我得到了一个派生的 class,它有两个不同的列表。在相等性的默认实现下,这些记录是不同的,因为它是通过引用相等性而不是序列相等性来比较列表。
如果我将基本记录上 Equals()
的默认实现覆盖为始终 return 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));
}
}
有趣的是,如果我更改实现,使 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; }
}
此外,此问题特定于记录:如果我将第一个示例的实现更改为使用 classes,则两个实例将评估为相等并且单元测试将通过。
public abstract class RecordBase
{
public virtual bool Equals(RecordBase obj)
{
return true;
}
}
因此,在尝试使用记录覆盖 Equals()
的默认实现时,派生记录不会继承基本记录的实现。
这有什么原因吗?直觉上,派生记录似乎应该能够继承基记录对基于值的相等性的实现。但是,我一直在通读 C# 9.0 specification for records,我不确定是否有一个合成的实现可以防止这种情况发生,或者这是否有可能使用记录。
不幸的是,记录的行为方式与您期望的不同。
当您声明一条记录时,您将免费获得相等性检查运算符和方法。
你的基础 class 只是 returns true
,但是当你将派生记录声明为 record
时,你也会在那里得到一个相等性检查方法,看起来像这样:
public virtual bool Equals(DerivedRecord other)
{
return (object)this == other || (base.Equals(other) &&
EqualityComparer<ICollection<int>>.Default.Equals(
this.TestCollection,
other.TestCollection));
}
由于EqualityComparer<ICollection<int>>.Default.Equals
做了一个简单的引用比较,所以这两个列表虽然内容相同,但不认为是相等的,因此你得到false
。
如果您将类型更改为仅 class
而不是 record
,则不会添加编译器生成的 Equals
方法和相关运算符,您只剩下一个从基数 class 开始,即 returns true
.
但是对于 record
类型,您将获得针对每个继承级别和类型的方法。这也是为什么直接在派生记录类型上实现它也能根据您的预期“工作”,从那时起编译器就不会为您生成一个。