Pierre de LESPINAY
Pierre de LESPINAY

Reputation: 46178

SequenceEqual not calling Equals in parent type

A parent type:

public class IdObject : IComparable<IdObject>, IEquatable<IdObject>
{
    public int id { get; set; }

    public bool Equals(IdObject other)
    {
        if (other == null) return this == null;
        if (this == null) return false;
        var test = other.id.CompareTo(this.id);

        return other.id.CompareTo(this.id) == 0;
    }

    public int CompareTo(IdObject other)
    {
        return other.id.CompareTo(this.id);
    }
}

A child:

public class NamedObject : IdObject
{
    public string name { get; set; }
}

Comparing lists of IdObjects

var list1 = new List<IdObject>()
{
    new IdObject() { id = 42 },
    new IdObject() { id = 43 }
};
var list2 = new List<IdObject>()
{
    new IdObject() { id = 43 },
    new IdObject() { id = 42 }
};
list1.Sort();
list2.Sort();
var test = list1.SequenceEqual(list2); // True

Comparing lists of Nameds

var list1 = new List<NamedObject>()
{
    new NamedObject() { id = 42 },
    new NamedObject() { id = 43 }
};
var list2 = new List<NamedObject>()
{
    new NamedObject() { id = 43 },
    new NamedObject() { id = 42 }
};
list1.Sort();
list2.Sort();
var test = list1.SequenceEqual(list2); // False

I realized that IdObject::Equals is not called through a NamedObject context.

Am I doing something wrong ?
Isn't supposed to call the inherited Equals ?
How can I use the parent's Equals ?

Upvotes: 1

Views: 672

Answers (1)

Jon Skeet
Jon Skeet

Reputation: 1500745

Basically, you've got a problem because your type doesn't override object.Equals(object) in a way consistent with your IEquatable<T> implementation and you're dealing with a collection of the subclasses.

SequenceEqual will be using EqualityComparer<NamedObject>.Default. That will check whether NamedObject implements IEquatable<NamedObject> - and will find that it doesn't, so it will fall back to calling object.Equals(object). You can see this here:

using System;
using System.Collections.Generic;

public class Base : IEquatable<Base>
{
    public override bool Equals(object other)
    {
        Console.WriteLine("Equals(object)");
        return false;
    }

    public bool Equals(Base other)
    {
        Console.WriteLine("Equals(Base)");
        return false;
    }

    public override int GetHashCode() => 0;
}

public class Derived : Base
{
}

public class Test
{
    static void Main()
    {
        var comparer = EqualityComparer<Derived>.Default;        
        Console.WriteLine(comparer.Equals(new Derived(), new Derived()));
    }
}

You don't override object.Equals(object), so you've effectively got reference equality.

I would recommend that you override object.Equals(object) and object.GetHashCode() in your base class.

You could then also implement IEquatable<NamedObject> in NamedObject, just delegating to the base implementation or (better) checking the name as well, unless you really don't want that to be taken into account.

Upvotes: 7

Related Questions