2

I'm looking for the best way to implement IEquatable<T> in such a way that type checking is implicit. If I call Equals in a child class, I want to ensure that the type I'm comparing to is the same. See the stripped down example below.

Two Child1 objects should be considered equal if their ID is the same. In this example, calling child1.Equals(child2) will return true if their ID is the same, which is not the intended behavior. I would basically like to force instances of Child1 to use an overload of Equals that requires a Child1 parameter, not simply a parameter that derives from the same base class as Child1.

I'm starting to think that I'm approaching this in the wrong way. Perhaps I should just leave the implementation of Equals in the base class, and ensure that this.GetType() == other.GetType()?

  public abstract class BaseClass : IEquatable<BaseClass>
  {
    public int ID { get; set; }

    public bool Equals(BaseClass other)
    {
      return 
        other != null &&
        other.ID == this.ID;
    }
  }

  public sealed class Child1 : BaseClass
  {
    string Child1Prop { get; set; }

    public bool Equals(Child1 other)
    {
      return base.Equals(other as BaseClass);
    }
  }

  public sealed class Child2 : BaseClass
  {
    string Child2Prop { get; set; }

    public bool Equals(Child2 other)
    {
      return base.Equals(other as BaseClass);
    }
  }
helrich
  • 1,300
  • 1
  • 15
  • 34
  • If you explicitly want a non-virtual equality operator use the `==` operator instead of `Equals`. That said, it's actually a common practice for `Equals` methods to compare the type of the argument and validate that it's not a sub-type. – Servy Aug 22 '14 at 16:41

3 Answers3

4

If you really need to "force instances of Child1 to use an overload of Equals that requires a Child1 parameter," you can do with C# Generics:

public abstract class BaseClass<T> : IEquatable<T>
    where T : BaseClass<T>
{
    public int ID { get; set; }

    public bool Equals(T other)
    {
        return
            other != null &&
            other.ID == this.ID;
    }
}

public sealed class Child1 : BaseClass<Child1>
{
    string Child1Prop { get; set; }

    public bool Equals(Child1 other)
    {
        return base.Equals(other);
    }
}

public sealed class Child2 : BaseClass<Child2>
{
    string Child2Prop { get; set; }

    public bool Equals(Child2 other)
    {
        return base.Equals(other);
    }
}

This makes sure that your "Equals" method can only be called with the type its defined in.

Michael
  • 3,099
  • 4
  • 31
  • 40
0

Can you compare the types of each, as well?

  return base.Equals(other as BaseClass) && this.GetType() is other.GetType();

This will compare the actual types that the variables were instantiated as. I created a .net fiddle, located here: https://dotnetfiddle.net/rOeuuo

Here is the code (in vb)

Imports System

Public Module Module1
    Public Sub Main()
        dim x as testparent = new testchild1
        dim y as testparent = new testchild2

        if x.GetType() is y.Gettype() then
            console.writeline("they are the same type")
        else
            console.writeline("they are different types")
        end if


    End Sub


End Module

public class testparent
    end class


    public class testchild1: inherits testparent

    end class


public class testchild2: inherits testchild1

    end class

And here's another fiddle where two child classes inherit directly from the base class, not from each other (I think this was your original question): https://dotnetfiddle.net/ViNqej

Imports System

Public Module Module1
    Public Sub Main()
        dim x as testparent = new testchild1
        dim y as testparent = new testchild2

        if x.GetType() is y.Gettype() then
            console.writeline("they are the same type")
        else
            console.writeline("they are different types")
        end if


    End Sub


End Module

public class testparent
    end class


    public class testchild1: inherits testparent

    end class


public class testchild2: inherits testparent

    end class
ps2goat
  • 8,067
  • 1
  • 35
  • 68
  • That would solve part of the problem, but I don't think doing that alone would solve it completely, because if I call `Equals` between a `Child1` and a `Child2` it will skip right down to the implementation in `BaseClass` anyways. – helrich Aug 22 '14 at 16:48
  • [Here's what I mean.](https://dotnetfiddle.net/DD66lf) If I add your suggestion to the base Equals function (commented in the fiddle), that does give the functionality I desire. – helrich Aug 22 '14 at 17:27
  • So the answer would be to do both? that's what I was getting at. The sample fiddles were only to prove that the `GetType()` method does not resolve to the base class. In `.Equals()`, compare the IDs and the types of the two objects to determine equality. – ps2goat Aug 22 '14 at 17:33
0

You have several options here:

  1. You can check the actual runtime type in each Equals method here and validate that it's not a sub-type, as you yourself suggested. This is in fact a reasonable solution.

  2. You can make each Equals method virtual. Have each derived type override their parent's Equals methods and either statically return false or try to cast to "your" type and return false if you can't.

  3. Use the == operator instead of Equals. It's definitions may or may not want to check the runtime types of the operands and validate that they are not sub-types.

A key behavior here is that you don't have to try to prevent users of your type from comparing your object to an object of another type. You actually have a sensible behavior other than just crashing. You can return false. You can compare a Tiger and an Elephant. They're different, but you can certainly compare them.

Servy
  • 202,030
  • 26
  • 332
  • 449