Equality and polymorphism

  • A+
Category:Languages

With two immutable classes Base and Derived (which derives from Base) I want to define Equality so that

  • equality is always polymorphic - that is ((Base)derived1).Equals((Base)derived2) will call Derived.Equals

  • operators == and != will call Equals rather than ReferenceEquals (value equality)

What I did:

class Base: IEquatable<Base> {   public readonly ImmutableType1 X;   readonly ImmutableType2 Y;    public Base(ImmutableType1 X, ImmutableType2 Y) {      this.X = X;      this.Y = Y;    }    public override bool Equals(object obj) {     if (object.ReferenceEquals(this, obj)) return true;     if (obj is null || obj.GetType()!=this.GetType()) return false;      return obj is Base o        && X.Equals(o.X) && Y.Equals(o.Y);   }    public override int GetHashCode() => HashCode.Combine(X, Y);    // boilerplate   public bool Equals(Base o) => object.Equals(this, o);   public static bool operator ==(Base o1, Base o2) => object.Equals(o1, o2);   public static bool operator !=(Base o1, Base o2) => !object.Equals(o1, o2);    } 

Here everything ends up in Equals(object) which is always polymorphic so both targets are achieved.

I then derive like this:

class Derived : Base, IEquatable<Derived> {   public readonly ImmutableType3 Z;   readonly ImmutableType4 K;    public Derived(ImmutableType1 X, ImmutableType2 Y, ImmutableType3 Z, ImmutableType4 K) : base(X, Y) {     this.Z = Z;      this.K = K;    }    public override bool Equals(object obj) {     if (object.ReferenceEquals(this, obj)) return true;     if (obj is null || obj.GetType()!=this.GetType()) return false;      return obj is Derived o       && base.Equals(obj) /* ! */       && Z.Equals(o.Z) && K.Equals(o.K);   }    public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), Z, K);    // boilerplate   public bool Equals(Derived o) => object.Equals(this, o); } 

Which is basically the same except for one gotcha - when calling base.Equals I call base.Equals(object) and not base.Equals(Derived) (which will cause an endless recursion).

Also Equals(C) will in this implementation do some boxing/unboxing but that is worth it for me.

My questions are -

First is this correct ? my (testing) seems to suggest it is but with C# being so difficult in equality I'm just not sure anymore .. are there any cases where this is wrong ?

and Second - is this good ? are there better cleaner ways to achieve this ?

 


Well I guess there are two parts to you problem:

  1. executing equals at nested level
  2. restricting to the same type

Would this work? https://dotnetfiddle.net/eVLiMZ (I had to use some older syntax as it didn't compile in dotnetfiddle otherwise)

using System;   public class Program {     public class Base     {         public string Name { get; set; }         public string VarName { get; set; }          public override bool Equals(object o)         {             return object.ReferenceEquals(this, o)                  || o.GetType()==this.GetType() && ThisEquals(o);         }          protected virtual bool ThisEquals(object o)         {             Base b = o as Base;             return b != null                 && (Name == b.Name);         }          public override string ToString()         {             return string.Format("[{0}@{1} Name:{2}]", GetType(), VarName, Name);         }          public override int GetHashCode()         {             return Name.GetHashCode();         }     }      public class Derived : Base     {         public int Age { get; set; }          protected override bool ThisEquals(object o)         {             var d = o as Derived;             return base.ThisEquals(o)                 && d != null                 && (d.Age == Age);         }          public override string ToString()         {             return string.Format("[{0}@{1} Name:{2} Age:{3}]", GetType(), VarName, Name, Age);         }          public override int GetHashCode()         {             return base.GetHashCode() ^ Age.GetHashCode();         }     }      public static void Main()     {         var b1 = new Base { Name = "anna", VarName = "b1" };         var b2 = new Base { Name = "leo", VarName = "b2" };         var b3 = new Base { Name = "anna", VarName = "b3" };         var d1 = new Derived { Name = "anna", Age = 21, VarName = "d1" };         var d2 = new Derived { Name = "anna", Age = 12, VarName = "d2" };         var d3 = new Derived { Name = "anna", Age = 21, VarName = "d3" };          var all = new object [] { b1, b2, b3, d1, d2, d3 };          foreach(var a in all)          {             foreach(var b in all)             {                 Console.WriteLine("{0}.Equals({1}) => {2}", a, b, a.Equals(b));             }         }     } }  

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: