4

I was having trouble with list.Sort() for a list of interface references that point to different types however the question Sort a list of interface objects provided the following solution solution

interface IFoo : IComparable<IFoo> 
{ 
    int Value { get; set; } 
}

class SomeFoo : IFoo
{
    public int Value { get; set; }

    public int CompareTo(IFoo other)
    {
        // implement your custom comparison here...
    }
}

In my original code rather than IFoo inherting from IComparable my class was inherting from both IFoo and ICompareable i.e.

interface IFoo
{ 
    int Value { get; set; } 
}

class SomeFoo : IFoo, IComparable<IFoo> 
{
    public int Value { get; set; }

    public int CompareTo(IFoo other)
    {
        // implement your custom comparison here...

    }
}
class SomeBar : IFoo, IComparable<IFoo> 
{
    public int Value { get; set; }

    public int CompareTo(IFoo other)
    {
        // implement your custom comparison here...
    }
}

But I was getting the error Failed to compare two elements in the array. when trying to sort a list of IFoo references.

List<IFoo> iFoos = new List<IFoo> 
{
    new SomeFoo{Value = 1},
    new SomeFoo{Value = 15},
    new SomeFoo{Value = 390},
    new SomeBar{Value = 2},
    new SomeBar{Value = 78},
    new SomeBar{Value = 134}
}

iFoos.Sort();

Can anyone explain why my original code did not work?

Community
  • 1
  • 1
Jack Blackmore
  • 117
  • 1
  • 10

4 Answers4

3

Your list is a list of IFoos. So from the perspective of the list (and its sort operation), it only sees that interface and does not know anything about the concrete types.

So when it tries to order two IFoos, it cannot do that because IFoo does not implement IComparable.

The problem is that just because both your types implement IComparable<Foo> separately, there is no guarantee that all IFoo elements in the list do so. So the operation is not safe.

In order to be able to sort the elements using IComparable<IFoo>, the IFoo interface needs to implement the interface itself.


Alternatively, you could also implement a IComparer<IFoo> and pass that to Sort() which then delegates to the respective actual implementation. Of course, this is not really an elegant solution and not very future proof (if you ever create a new implementation of IFoo):

class FooComparer : IComparer<IFoo>
{
    public int Compare(IFoo a, IFoo b)
    {
        if (a is SomeFoo)
            return ((SomeFoo)a).CompareTo(b);
        else if (a is SomeBar)
            return ((SomeBar)a).CompareTo(b);
        else
            throw new NotImplementedException("Comparing neither SomeFoo nor SomeBar");
    }
}

Of course, if you mean IFoo to be comparable, you should have that interface implement IComparable<IFoo> directly instead of relying on subtypes to do so. IFoo is a contract, and being sortable is a fine property to require.

poke
  • 369,085
  • 72
  • 557
  • 602
  • I tried to run it, and you are correct, now the interesting part is that even if you declare the list as `List> iFoos = new List>()` same exception is thrown. Why would that be? – pijemcolu Jul 04 '16 at 12:50
  • @pijemcolu Because now, you have a list of `IComparable`; so in order to compare those objects now, it looks for `IComparable>`. – poke Jul 04 '16 at 12:54
1

Nice question with a twist!

When you are sorting a type, you expect the type to implement IComparable.

In your original code you are sorting IFoo which does not implement IComparable but in the second one it does. And that has made all the difference.

But if you had a collection of List<SomeBar> it would sort because it has IComparable implemented. Ignoring that you probably would need to use an interface of List I suggest you to use your second solution.

Carbine
  • 7,849
  • 4
  • 30
  • 54
  • I think it's just clicked, if my classes `SomeFoo` and `SomeBar` implement `ICompareable` I could sort a list of `SomeFoo` or a list of `SomeBar` but because my interface `IFoo` doesn't implement `ICompareable` a list of `IFoo` can't be sorted? – Jack Blackmore Jul 04 '16 at 12:50
  • Exactly, thats the problem. You got it. – Carbine Jul 04 '16 at 12:50
  • 1
    @JackBlackmore Yes, indeed. The compiler will infer the interface type (read transform `SomeFoo` to `(IFoo)SomeFoo`), so the list will effectively contains ONLY interfaces; not derived instances. As your interface doesn't implement IComparable, the program can't know how to compare different implementations of it. – gobes Jul 04 '16 at 12:56
1

This behaviour is described in the documentation:

This method uses the default comparer Comparer<T>.Default for type T to determine the order of list elements. The Comparer<T>.Default property checks whether type T implements the IComparable generic interface and uses that implementation, if available. If not, Comparer.Default checks whether type T implements the IComparable interface. If type T does not implement either interface, Comparer.Default throws an InvalidOperationException.

Since T is IFoo, your first example works when IFoo implements IComparable<IFoo> and the second fails because it does not. I suggest you create a class FooComparer : IComparer<Foo> and pass that to the other overload of Sort.

Lee
  • 142,018
  • 20
  • 234
  • 287
  • Thank you Lee, I really need to start getting my head around how the documentation is worded. It's hard to follow but it really is all there! – Jack Blackmore Jul 04 '16 at 12:57
0

In your first example IFoo implements IComparable<IFoo> and in the second one does not. And if you use Array.Sort or any other sort routine it will cause an ArgumentException that at least one object should implement IComparable.

Andriy Tolstoy
  • 5,690
  • 2
  • 31
  • 30