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

Custom Equality check for C# 9 records

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

简而言之,对于像这样实现的record Foovar foo = new Foo { Value = "foo" }var bar = new Foo { Value = "foo" }foo == bar表达式将导致True,甚至尽管他们有不同的参考 (ReferenceEquals(foo, bar) // False)。

现在有了记录,即使在 the article 发布在 .Net 博客中,它说:

If you don’t like the default field-by-field comparison behaviour of the generated Equals override, you can write your own instead.

当我尝试放置 public override bool Equals、或 public override int GetHashCode、或 public static bool operator == 等时,我遇到了 Member with the same signature is already declared 错误,所以我认为这是一个受限制的行为,struct 对象不是这种情况。

Failing example:

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);
}

编译结果:

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

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

有人想要覆盖记录相等性的一个用例是因为您可以有一个 attribute that would exclude a property from equality check. Take for example this ValueObject 实现。

然后,如果你像这样扩展这个 ValueObject 摘要 class:

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}";
}

那么你会得到以下 results:

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

到目前为止,为了以某种方式实现上述用例,我已经实现an abstract record object并像这样利用它:

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

    public string Surname;

    [IgnoreMember]
    public string FullName => $"{Name} {Surname}";
}

结果如下所示:

var user1 = new FullNameVo
{
    Name    = "John",
    Surname = "Doe"
};

var user2 = new FullNameVo
{
    Name    = "John",
    Surname = "Doe"
};

var user3 = user1 with { Name = "Jane" };

Console.WriteLine(user1 == user2); // True
Console.WriteLine(ReferenceEquals(user1, user2)); // False
Console.WriteLine(user1 == user3); // False
Console.WriteLine(user1.Equals(user3)); // False
Console.WriteLine(ValueObject.EqualityComparer.Equals(user1, user3)); // True

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

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

根据 the C#9 record proposal,以下 应该编译 ,即使在没有实际实现的情况下不是很有用..

// 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!
}

equality-related 成员有限制,因为大部分代码是合成的。该提案包括预期合成基础类型的示例。

也就是说,给定只是一个Equals(R?),编译器会创建一个==!=Equals(object)。 可以定义的方法可以在提案中搜索“user-defined”

尝试override/define其他相等方法或运算符预期失败:

It is an error if the override is declared explicitly.

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

The record type implements System.IEquatable<R> and includes a synthesized strongly-typed overload of book Equals(R? other) where R is the record type. The method is public, and the method is virtual unless the record type is sealed. The [Equals(R?)] method can be declared explicitly. It is an error if the explicit declaration does not match the expected signature or accessibility, or the explicit declaration doesn't allow overriding it in a derived type and the record type is not sealed. If Equals(R? other) is user-defined (not synthesized) but GetHashCode is not [user-defined], a warning is produced.