C# 9 记录的自定义相等检查

pan*_*sru 24 c# equality record c#-9.0

据我了解,记录实际上是类,它们以您的对象是值驱动而不是引用驱动的方式实现自己的相等性检查。

简而言之,对于record Foo像这样实现的 : var foo = new Foo { Value = "foo" }and ,即使它们有不同的引用 ( ) var bar = new Foo { Value = "foo" }foo == bar表达式也会导致。TrueReferenceEquals(foo, bar) // False

现在有了记录,即使在 .Net 博客上发表的文章中,它说:

如果您不喜欢生成的 Equals 覆盖的默认逐字段比较行为,您可以编写自己的。

当我尝试放置public override bool Equals, or public override int GetHashCode, orpublic static bool operator ==等时,我遇到了Member with the same signature is already declared错误,所以我认为这是一种受限制的行为,而struct对象并非如此。

失败的例子

public sealed record SimpleVo
    : IEquatable<SimpleVo>
{
    public bool Equals(SimpleVo other) =>
        throw new System.NotImplementedException();

    public override bool Equals(object obj) =>
        obj is SimpleVo other && Equals(other);

    public override int GetHashCode() =>
        throw new System.NotImplementedException();

    public static bool operator ==(SimpleVo left, SimpleVo right) =>
        left.Equals(right);

    public static bool operator !=(SimpleVo left, SimpleVo right) =>
        !left.Equals(right);
}
Run Code Online (Sandbox Code Playgroud)

编译结果:

SimpleVo.cs(11,30): error CS0111: Type 'SimpleVo' already defines a member called 'Equals' with the same parameter types

SimpleVo.cs(17,37): error CS0111: Type 'SimpleVo' already defines a member called 'op_Equality' with the same parameter types

SimpleVo.cs(20,37): error CS0111: Type 'SimpleVo' already defines a member called 'op_Inequality' with the same parameter types
Run Code Online (Sandbox Code Playgroud)

我的主要问题是,如果我们想自定义平等检查器的工作方式怎么办?我的意思是,我确实明白这违背了记录的全部目的,但另一方面,平等检查器并不是使记录使用起来很酷的唯一功能。

有人想要覆盖记录的相等性的一个用例是因为您可能有一个属性从相等性检查中排除一个属性。以这个 ValueObject实现为例。

然后,如果你ValueObject像这样扩展这个抽象类:

SimpleVo.cs(11,30): error CS0111: Type 'SimpleVo' already defines a member called 'Equals' with the same parameter types

SimpleVo.cs(17,37): error CS0111: Type 'SimpleVo' already defines a member called 'op_Equality' with the same parameter types

SimpleVo.cs(20,37): error CS0111: Type 'SimpleVo' already defines a member called 'op_Inequality' with the same parameter types
Run Code Online (Sandbox Code Playgroud)

那么你会得到以下结果

public sealed class FullNameVo : ValueObject
{
    public FullNameVo(string name, string surname)
    {
        Name    = name;
        Surname = surname;
    }

    [IgnoreMember]
    public string Name { get; }

    public string Surname { get; }

    [IgnoreMember]
    public string FullName => $"{Name} {Surname}";
}
Run Code Online (Sandbox Code Playgroud)

到目前为止,为了以某种方式实现上述用例,我实现了一个抽象记录对象并像这样使用它:

var user1 = new FullNameVo("John", "Doe");
var user2 = new FullNameVo("John", "Doe");
var user3 = new FullNameVo("Jane", "Doe");

Console.WriteLine(user1 == user2); // True
Console.WriteLine(ReferenceEquals(user1, user2)); // False
Console.WriteLine(user1 == user3); // True
Console.WriteLine(user1.Equals(user3)); // True
Run Code Online (Sandbox Code Playgroud)

结果如下所示:

public sealed record FullNameVo : ValueObject
{
    [IgnoreMember]
    public string Name;

    public string Surname;

    [IgnoreMember]
    public string FullName => $"{Name} {Surname}";
}
Run Code Online (Sandbox Code Playgroud)

总而言之,我有点困惑,限制记录对象的相等方法的覆盖是预期行为还是因为它仍处于预览阶段?如果是设计使然,您会以不同(更好)的方式实现上述行为,还是继续使用类?

dotnet --version 输出: 5.0.100-rc.1.20452.10

use*_*740 9

根据C#9 记录提案,以下内容应该编译,即使在没有实际实现的情况下不是很有用..

// No explicit IEquatable<R> - this is synthesized!
public sealed record SimpleVo
{
    // Not virtual, as SimpleVo (R) is sealed.
    // Accepts SimpleVo? (R?), and not SimpleVo (R), as argument.
    public bool Equals(SimpleVo? other) =>
        throw new System.NotImplementedException();

    // Optional: warning generated if not supplied when Equals(R?) is user-defined.
    public int GetHashCode() =>
        throw new System.NotImplementedException();

    // No other “standard” equality members!
}
Run Code Online (Sandbox Code Playgroud)

由于大部分代码都是合成的,因此对与等式相关的成员有限制。该提案包括预期合成基础类型的示例。

也就是说,由于只是一个Equals(R?)编译器创建一个==!=Equals(object)。可以通过在提案中搜索“用户定义”来找到可以定义的方法。

尝试覆盖/定义其他相等方法或运算符预计会失败:

如果显式声明了覆盖,则会出现错误。

该行为在“平等成员”中进行了讨论,并在以下段落中进行了总结:

记录类型实现System.IEquatable<R>并包括一个综合的强类型重载,book Equals(R? other)其中 R 是记录类型。该方法是公共的,除非记录类型是密封的,否则该方法是虚拟的。[ Equals(R?)] 方法可以显式声明。如果显式声明与预期的签名或可访问性不匹配,或者显式声明不允许在派生类型中覆盖它并且记录类型未密封,则这是一个错误。如果Equals(R? other)是用户定义(未合成)但GetHashCode不是 [用户定义],则会产生警告。

  • 嗯,为什么当我尝试在没有 override 关键字的情况下编写 GetHashCode 时,我的编译器会抱怨?奇怪的是,似乎 GetHashCode 必须被继承/重写,而 Equals 将被合成/调用。 (6认同)