提问人:Bartosz 提问时间:7/21/2022 最后编辑:Bartosz 更新时间:7/23/2022 访问量:493
C# 记录类中的自定义 EqualityContract
Custom EqualityContract in a C# record class
问:
在 C# 记录类中为属性提供自定义实现的正确方法和预期用途是什么?System.Type EqualityContract { get; }
默认(综合)实现返回记录类。因此,只能将 type 的实例与 类型的其他实例进行比较,否则结果始终为 。这在典型情况下非常有意义。但是,允许将该默认实现替换为自定义实现,因此必须有原因。我希望它允许多个记录类共享相等契约,例如,如果它们具有相同的实例属性或它们派生自相同的基本记录类。但是,这需要声明 和 的自定义覆盖,这是被禁止的。typeof(R)
R
R
R
false
bool Equals(object? other)
bool Equals(Base? other)
[编辑:请注意,问题是关于定义自定义平等合同的目的和可用性。下面的例子只是为了展示一个潜在的用途,但这个用途是行不通的。我不是在问如何使用不同的机制使示例工作。
例如,假设我希望一个记录类重用其基本记录类的相等协定,以便允许在 的实例之间进行比较,并基于 的实例属性。我需要做这样的事情:Derived
Base
Base
Derived
Base
record Base(int X);
record Derived(int X) : Base(X) {
protected override System.Type EqualityContract => base.EqualityContract;
public virtual bool Equals(Derived? other) => base.Equals(other);
public sealed override bool Equals(Base? other) => base.Equals(other); // forbidden
public override bool Equals(object? other) => base.Equals(other); // forbidden
/* some additional stuff */
}
我不能这样做,因为在 C# 记录中禁止声明自定义重写。如果没有禁止的自定义覆盖,记录类将如下所示(包含用于说明的合成覆盖):Equals
Derived
Equals
record Derived(int X) : Base(X) {
protected override System.Type EqualityContract => base.EqualityContract;
public virtual bool Equals(Derived? other) => base.Equals(other);
public sealed override bool Equals(Base? other) => Equals((object)other); // synthesized
public override bool Equals(object? other) => Equals(other as Derived); // synthesized
/* some additional stuff */
}
然后,相等契约无法正常工作。例如,以下代码生成:True False
Base obj1 = new Base(1);
Derived obj2 = new Derived(1);
System.Console.WriteLine($"{obj1 == obj2} {obj2 == obj1}");
这两种比较都使用 的运算符 ,该运算符调用方法 。对于 ,使用了 from 的实现,它比较了相等契约(在本例中相等)和 的值(在本例中相等)并返回 。对于 ,使用了 from 的实现,这会导致对 的调用,该调用返回 ,因为 是 。==
Base
bool Equals(Base? other)
obj1 == obj2
Base
X
true
obj2 == obj1
Derived
obj2.Equals(obj1 as Derived)
false
obj1 as Derived
null
那么,我应该如何利用自定义平等合同呢?我错过了什么吗?允许自定义覆盖有什么问题?Equals
我搜索了 C# 设计存储库中的讨论以查找一些信息。我发现的仅有的两个相关评论表明,我上面的方案是可行的:https://github.com/dotnet/csharplang/issues/3137#issuecomment-581558013 https://github.com/dotnet/csharplang/discussions/3787#discussioncomment-130523
答:
据我了解.NET6 无法实现记录的镜像相等性。如您所示,您可以使用,但另一种方式是不可能的。true
base1 == derived1
EqualityContract
问题在于,虽然 in 将被调用,但它将被调用 for,正如您正确指出的那样,覆盖另一个是被禁止的。Equals(Derived? other)
Derived
null
derived1 == base1
Equals
在我看来,这不是问题,因为:
- 没有额外字段的派生记录是值得怀疑的。
- 如果记录具有不同的 flield,则 compare-only-x 行为会令人困惑。
您的问题的解决方案可能是自定义相等比较器。(如果你不了解它们,官方的 EqualityComparer 类有一个很好的例子,其中有两本字典用于相同类型但具有不同的相等比较器。
拥有:
record Base(int X) {}
record Derived(int X, int Y) : Base(X) {}
class CheckOnlyXComparer : EqualityComparer<Base>
{
public override bool Equals(Base? b1, Base? b2)
{
if (b1 == null && b2 == null) return true;
if (b1 == null || b2 == null) return false;
return (b1.X == b2.X);
}
public override int GetHashCode(Base b) => b.X.GetHashCode();
}
以下代码片段
WriteLine(base1 == derived1);
WriteLine(derived1 == base1);
var comparer = new CheckOnlyXComparer();
WriteLine(comparer.Equals(base1,derived1));
WriteLine(comparer.Equals(derived1,derived1));
指纹
False
False
True
True
为
List<Base> l1 = new (){base1, derived1};
WriteLine(l1.Count());
var distinctByX = l1.Distinct(new CheckOnlyXComparer());
WriteLine(distinctByX.Count());
我们得到
2
1
评论
EqualityContract
允许派生类型“禁用”类型检查,恕我直言,它已经提供了很好的价值。base == derived
EqualityContract
base == derived
评论