C# 记录类型:记录子类 之间的相等比较
C# Record Types: Equality comparisons between record sub-classes
给定父记录类型:
public record Foo(string Value);
和两个记录子 classes Bar
和 Bee
我想知道是否可以在基础 class 中实现 Equals
以便Foo、Bar 或 Bee 的实例都被认为是 相等 基于 Value
(两者都具有 Equals
和 ==
)。
我在消化 https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/records 后尝试了以下方法,但效果不佳:
public record Foo(string Value)
{
public virtual bool Equals(Foo? other)
{
return other != null && this.Value == other.Value;
}
public override int GetHashCode() => this.Value.GetHashCode();
}
public record Bar(string Value) : Foo(Value)
{
protected override Type EqualityContract => typeof(Foo);
}
public record Bee(string Value) : Foo(Value)
{
protected override Type EqualityContract => typeof(Foo);
}
[Test]
public void TestFooBar()
{
Assert.That(new Foo("foo") == new Bar("foo"), Is.True); // Passes
Assert.That(new Bar("foo") == new Foo("foo"), Is.True); // Fails!
}
[Test]
public void TestFooBee()
{
Assert.That(new Foo("foo") == new Bee("foo"), Is.True); // Passes
Assert.That(new Bee("foo") == new Foo("foo"), Is.True); // Fails!
}
[Test]
public void TestBarBee()
{
Assert.That(new Bar("foo") == new Bee("foo"), Is.True); // Fails!
Assert.That(new Bee("foo") == new Bar("foo"), Is.True); // Fails!
}
此问题特定于记录类型。我不需要 classes 的示例(我已经知道了)。
当我查看 sharplab.io 时,我看到 Bar
的以下实现:
[System.Runtime.CompilerServices.NullableContext(2)]
public override bool Equals(object obj)
{
return Equals(obj as Bar);
}
[System.Runtime.CompilerServices.NullableContext(2)]
public sealed override bool Equals(Foo other)
{
return Equals((object)other);
}
并且在 Foo
中:
[System.Runtime.CompilerServices.NullableContext(2)]
public static bool operator ==(Foo left, Foo right)
{
if ((object)left != right)
{
if ((object)left != null)
{
return left.Equals(right);
}
return false;
}
return true;
}
[System.Runtime.CompilerServices.NullableContext(2)]
public override bool Equals(object obj)
{
return Equals(obj as Foo);
}
[System.Runtime.CompilerServices.NullableContext(2)]
public virtual bool Equals(Foo other)
{
if (other != null)
{
return Value == other.Value;
}
return false;
}
所以自然地 new Bar("foo") == new Bee("foo")
最终会调用 Bar.Equals(Foo?)
,它是合成的并且依赖于 Bar.Equals(object)
,它将转换为 Bar
或 return null,并且因为 Bee
不是 Bar
它最终比较 null.
似乎无法覆盖某些合成的 Equals 方法,因此我似乎无法避免这种行为。
或者,我可以吗?
注意:我使用的是 .NET SDK 6.0。
这是调用 new Bar("foo") == new Foo("foo")
:
时的堆栈跟踪
at Foo.Equals(Foo other)
at Bar.Equals(Bar other)
at Bar.Equals(Object obj)
at Bar.Equals(Foo other)
at Foo.op_Equality(Foo r1, Foo r2)
根据 draft spec,除了 Foo.Equals(Foo)
和 Bar.Equals(Bar)
,您不能显式声明任何这些方法(即无法控制它们),此时 other
已经(未成功)转换为 Bar
,在 Bar.Equals(Object)
.
虽然这并非完全不可能 - 您可以手动声明运算符 ==(Bar, Foo)
、!=(Bar, Foo)
等,并让运算符重载决策选择您的运算符,而不是 ==(Foo, Foo)
一个,你无法控制的。但这对于所有类型来说都非常乏味,而且您仍然会遇到 Bar.Equals(Foo)
无法按您想要的方式工作的问题。 :(
EqualityContract
是无关紧要的。仅在生成的 Foo.Equals(Foo)
,
中检查
The synthesized Equals(R?)
returns true if and only if each of the following are true:
- [...]
- If there is a base record type, the value of
base.Equals(other)
(a non-virtual call to public virtual bool Equals(Base? other)
); otherwise the value of EqualityContract == other.EqualityContract
.
但此时 other
已经为空!更何况你写了自己的 Foo.Equals(Foo)
,所以 EqualityContract
根本没有用。
给定父记录类型:
public record Foo(string Value);
和两个记录子 classes Bar
和 Bee
我想知道是否可以在基础 class 中实现 Equals
以便Foo、Bar 或 Bee 的实例都被认为是 相等 基于 Value
(两者都具有 Equals
和 ==
)。
我在消化 https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/records 后尝试了以下方法,但效果不佳:
public record Foo(string Value)
{
public virtual bool Equals(Foo? other)
{
return other != null && this.Value == other.Value;
}
public override int GetHashCode() => this.Value.GetHashCode();
}
public record Bar(string Value) : Foo(Value)
{
protected override Type EqualityContract => typeof(Foo);
}
public record Bee(string Value) : Foo(Value)
{
protected override Type EqualityContract => typeof(Foo);
}
[Test]
public void TestFooBar()
{
Assert.That(new Foo("foo") == new Bar("foo"), Is.True); // Passes
Assert.That(new Bar("foo") == new Foo("foo"), Is.True); // Fails!
}
[Test]
public void TestFooBee()
{
Assert.That(new Foo("foo") == new Bee("foo"), Is.True); // Passes
Assert.That(new Bee("foo") == new Foo("foo"), Is.True); // Fails!
}
[Test]
public void TestBarBee()
{
Assert.That(new Bar("foo") == new Bee("foo"), Is.True); // Fails!
Assert.That(new Bee("foo") == new Bar("foo"), Is.True); // Fails!
}
此问题特定于记录类型。我不需要 classes 的示例(我已经知道了)。
当我查看 sharplab.io 时,我看到 Bar
的以下实现:
[System.Runtime.CompilerServices.NullableContext(2)]
public override bool Equals(object obj)
{
return Equals(obj as Bar);
}
[System.Runtime.CompilerServices.NullableContext(2)]
public sealed override bool Equals(Foo other)
{
return Equals((object)other);
}
并且在 Foo
中:
[System.Runtime.CompilerServices.NullableContext(2)]
public static bool operator ==(Foo left, Foo right)
{
if ((object)left != right)
{
if ((object)left != null)
{
return left.Equals(right);
}
return false;
}
return true;
}
[System.Runtime.CompilerServices.NullableContext(2)]
public override bool Equals(object obj)
{
return Equals(obj as Foo);
}
[System.Runtime.CompilerServices.NullableContext(2)]
public virtual bool Equals(Foo other)
{
if (other != null)
{
return Value == other.Value;
}
return false;
}
所以自然地 new Bar("foo") == new Bee("foo")
最终会调用 Bar.Equals(Foo?)
,它是合成的并且依赖于 Bar.Equals(object)
,它将转换为 Bar
或 return null,并且因为 Bee
不是 Bar
它最终比较 null.
似乎无法覆盖某些合成的 Equals 方法,因此我似乎无法避免这种行为。
或者,我可以吗?
注意:我使用的是 .NET SDK 6.0。
这是调用 new Bar("foo") == new Foo("foo")
:
at Foo.Equals(Foo other)
at Bar.Equals(Bar other)
at Bar.Equals(Object obj)
at Bar.Equals(Foo other)
at Foo.op_Equality(Foo r1, Foo r2)
根据 draft spec,除了 Foo.Equals(Foo)
和 Bar.Equals(Bar)
,您不能显式声明任何这些方法(即无法控制它们),此时 other
已经(未成功)转换为 Bar
,在 Bar.Equals(Object)
.
虽然这并非完全不可能 - 您可以手动声明运算符 ==(Bar, Foo)
、!=(Bar, Foo)
等,并让运算符重载决策选择您的运算符,而不是 ==(Foo, Foo)
一个,你无法控制的。但这对于所有类型来说都非常乏味,而且您仍然会遇到 Bar.Equals(Foo)
无法按您想要的方式工作的问题。 :(
EqualityContract
是无关紧要的。仅在生成的 Foo.Equals(Foo)
,
The synthesized
Equals(R?)
returns true if and only if each of the following are true:
- [...]
- If there is a base record type, the value of
base.Equals(other)
(a non-virtual call topublic virtual bool Equals(Base? other)
); otherwise the value ofEqualityContract == other.EqualityContract
.
但此时 other
已经为空!更何况你写了自己的 Foo.Equals(Foo)
,所以 EqualityContract
根本没有用。