3

in the code below calling SequenceEqual on generic list return true (as expected) when List is defined with class generic type (EquatableClass.Equals<> is called).

If list is defined with IEquatable interface, Equals method is not called and result is false (object.Equals is called instead, not in code).

The question is, why the EquatableClass.Equals<> method is not called in the second case?

public class EquatableClass : IEquatable<EquatableClass>
{
        public string Name { get; set; }
        public bool Equals(EquatableClass other) => this.Name.Equals(other.Name);        
}

static void Main(string[] args)
{
    var A = new List<EquatableClass> {  new EquatableClass { Name = "A" } };
    var B = new List<EquatableClass> {  new EquatableClass { Name = "A" } };

    var result1 = A.SequenceEqual(B); // == true;

    var AA = new List<IEquatable<EquatableClass>> {  new EquatableClass { Name = "A" } };
    var BB = new List<IEquatable<EquatableClass>> {  new EquatableClass { Name = "A" } };

    var result2 = AA.SequenceEqual(BB); // == false;    
}
Servy
  • 202,030
  • 26
  • 332
  • 449
  • Because the parameters don't match the class parameters. So the default method is used. – jdweng Mar 13 '17 at 14:39
  • This is the reason that if you are implementing `IEquatable` you should override the behavior of `Equals(object)` too to match the behavior. – Scott Chamberlain Mar 13 '17 at 14:44
  • Always also override `Equals`(+ `GetHashCode`), f.e.: `public override bool Equals(object obj) { EquatableClass other = obj as EquatableClass; return obj != null && other.Equals(this); }` – Tim Schmelter Mar 13 '17 at 14:45

1 Answers1

1

The SequenceEqual<T> method will try to see if it can convert T to IEquatable<T>, and if it can, it will use IEquatable<T>.Equals for equality.

When you have a List<EquatableClass> it will then try to convert EquatableClass to IEquatable<EquatableClass>, and that succeeds, so it uses the appropriate Equals method.

When you have a List<IEquatable<EquatableClass>> it will then try to convert IEquatable<EquatableClass> to IEquatable<IEquatable<EquatableClass>>, and that will fail, because the actual object doesn't implement IEquatable<IEquatable<EquatableClass>>, so it resorts to the default behavior of using object.Equals(object), which you don't override.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • Ok, as I understand, it can be reduced to: `typeof(IEquatable>).IsAssignableFrom(typeof(EquatableClass)) == false` and the reason is because IEquatable interface generic parameter is declared as invariant, am I correct? – user4662448 Mar 14 '17 at 15:00
  • @user4662448 It wouldn't be any different if `IEquatable` were covariant or contravariant. That conversion wouldn't be applicable in any of those situations for that type. It would need to actually implement `IEquatable>` and add the extra `Equals` method for that to work. (And that's clearly not a good idea). – Servy Mar 14 '17 at 15:05
  • But adding extra `Equals` don't work, object.Equals is called instead... – user4662448 Mar 14 '17 at 15:24
  • @user4662448 If you add a second `IEquatable` implementation it would be used. You don't want to do that. The bug is that you're typing the list as `IEquatable` when you shouldn't be. The correct solution is to use a list of the types you actually have the ability to compare, which is an `EquatableClass` not an `IEquatable>`, but if you add the additional `IEquatable` implementation you'd at least see what's going on, although you shouldn't keep it in your code. – Servy Mar 14 '17 at 15:26
  • Servy, you wrote about additional `IEquatable>` implementation, right? But when I add the implementation into the `EquatableClass` the `Equals(IEquatable other)` method is not called.. why? (my personal opinion is that it is somehow related with invariancy of IEquatable ... but I might be wrong). – user4662448 Mar 14 '17 at 16:40
  • @user4662448 No, like I said, the variance of the interface isn't relevant here. Without seeing what you'd written, I couldn't say why it's not behaving as you expect. It's a tangent off of your question anyway though, so I wouldn't worry about it. – Servy Mar 14 '17 at 16:52
  • Sure. Thanks for answers. – user4662448 Mar 15 '17 at 09:45