Given a parent record type:
public record Foo(string Value);
And two record sub-classes Bar and Bee I wonder if it is possible to implement Equals in the base class so as instances of Foo, Bar or Bee are all considered equal based on Value (both with Equals and ==).
I tried the following after digesting https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/records, but it didn't quite work:
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!
}
This question is specific to record types. I don't need an example with classes (I know that already).
When I look at sharplab.io, I see the following implementation for 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);
}
And in 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;
}
So naturally new Bar("foo") == new Bee("foo") would end-up calling Bar.Equals(Foo?), which is synthetic and relies on Bar.Equals(object) which will cast to Bar or return null, and because a Bee is not a Bar it ends-up comparing null.
It doesn't seem possible to override some of the synthetic Equals methods so it seems I cannot escape this behavior.
Or, can I?
NOTE: I'm on .NET SDK 6.0.