1

This is more of an academic exercise than anything else, so I'm basically just trying to get my head around how to use IComparable when the types are different.

So say we have have a fruit class, and derived classes "Apple" and "Orange". Say that I want to a list of fruit to have all the apples before the oranges. What is the best way of going about this?

I think you could just make Fruit implement the interface IComparable and put a bunch of conditional statements in for very subtypes, but this seems very crude to me and probably violates the open/closed principle. I'm more interested in getting it to work this way:

public abstract class Fruit : IComparable<Fruit>
{
    public abstract int CompareTo(Fruit other);
}

public class Apple : Fruit, IComparable<Orange>
{
    public override int CompareTo(Fruit other)
    {
        if(other is Orange)
        {
            this.CompareTo((Orange)other);
        }
        return 0;
    }

    public virtual int CompareTo(Orange other)
    {
        return -1;
    }
}

public class Orange : Fruit, IComparable<Apple>
{
    public override int CompareTo(Fruit other)
    {
        if (other is Apple)
        {
            this.CompareTo((Apple)other);
        }
        return 0;
    }

    public virtual int CompareTo(Apple other)
    {
        return 1;
    }
}

My main goal here is to get IComparable working with cross types. I tried loading a list up with various fruit but alas it did not sort. Perhaps my understanding of the return value of CompareTo is a bit wonky. Is there any hope for this type of method and is there any scenario where it might be more useful than the obvious approach?

pseudoabdul
  • 616
  • 3
  • 12
  • Is there a condition where you will see orange equal to apple? If so you would need IComparable. If they are never going to be considered equal, then a simple type compare is enough. – Carbine Jul 26 '18 at 08:56
  • What if you also have bananas? Are those allways greater apples but smaller oranges? Or ...? Having said this your *academic question* is quite pointless, as you can´t assume your base-class has only two ancestors. In fact a base-class should not even *know* anything of its ancestors. – MakePeaceGreatAgain Jul 26 '18 at 08:57
  • If the goal is just to sort an array of objects, consider using `IEnumerable.OrderByDescending`, as so `var sortedFruits = fruits.OrderByDescending(x => x is Apple);` – Claus Jørgensen Jul 26 '18 at 09:02
  • Conflicting CompareTo() implementations. Apple.CompareTo says that an apple is always less than an orange. Orange.CompareTo says that an orange is always less than an apple. Use return 1; in one of them and try again. – Hans Passant Jul 26 '18 at 09:14

3 Answers3

2

I think it feels kinda wonky is because there is no natural order of Apples and Oranges. In this specific instance you prefer apples over oranges, but maybe the next guy wants it the other way round. Or it's a mix in the winter? The point is that Apples and Oranges don't have one unique sorting alorithm and it feels wrong to build it into either Apples or Oranges or even Fruit.

That is where IComparer comes in. You can put your comparison logic in there, but you can have many comparers and select another one with each sort you do. So you implement an ApplesFirstComparer for the winter and then an OrangesWithTheMostOrangeColorOnTopDoNotCareForColorOfApplesComparer and another one and another one. Basically one for each comparison you need, without implying that Apples and Oranges have a natural order. Because they don't.

nvoigt
  • 75,013
  • 26
  • 93
  • 142
  • Each comparison is always built by fiat. There is no bible-mandated-rule that the ordering A-Z is better than the ordering Z-A, or D-C-B-A-EtoZ. You can have a "default" ordering and then specific orderings. Remaining in the string field, you have the codepoint ordering and then you have the national collations. – xanatos Jul 26 '18 at 09:02
  • @xanatos Sure, but some things have a *defined* order that's probably the same understanding world wide. You *can* order dates. Or money. Or shirt sizes. And most people will agree and think it's intuitive. Ordering different kinds of fruit is not intuitive regardless of what you pick. One guy might order them by color, the next by calories and both could claim they are "right". That's why I would prefer an external comparer. To me that signals that it's one way to compare fruit, while an implemented `IComparable<>`would signal to me that there is an order that is obvious. – nvoigt Jul 26 '18 at 09:06
  • You only have to document it, you don't really have to justify it. I don't think the persons that wrote the various DOS codepages justified the position of the characters :-) You are applying an "ipse dixit" approach here (that is correct, to be clear, I would probably do the same): if someone else decided it (probably by fiat), then it is ok to use it. If I do the same (decide something) it is wrong. To be clear, my only point is here: you can decide by fiat that apples are better than kiwis. Sometimes you have to do it. The important thing is to document. Still you are correct. – xanatos Jul 26 '18 at 09:12
1

It will become terrible immediately... Each fruit must know of each other fruit... If you have 10 fruits, you have 90 pieces of code just to decide how to compare them.

I would do something like:

public abstract class Fruit : IComparable<Fruit>
{
    // It should be unique for each fruit type
    public abstract int Importance { get; }

    public int CompareTo(Fruit other)
    {
        // If you want, you can do some tests here, that 
        // are common to all the Fruit. I wouldn't,
        // because this would create an ordering with
        // higher priority than Importance.

        int cmp = Importance.CompareTo(other.Importance);

        if (cmp != 0)
        {
            return cmp;
        }

        if (GetType() != other.GetType())
        {
            throw new ApplicationException("Different type of fruit must have different Importance");
        }

        // Other Fruit comparisons

        // We know the Fruit have the same type (see above)
        return CompareToInternal(other);
    }

    // Comparison of subtype of Fruit
    public abstract int CompareToInternal(Fruit other);
}

So only Fruit of the same type are really comparable. Other fruits have an Importance that is predetermined (apples are better than kiwis), and there is an abstract CompareToInternal to do the subtype comparisons (inside the same type of fruit... apple vs apple, kiwi vs kiwi)

xanatos
  • 109,618
  • 12
  • 197
  • 280
1

This is my idea. It seems simple but it will work.

You can mark the unique order for each class and sort it.

public abstract class Fruit
{
    public int MyOrder {get;}
}

public class Apple : Fruit
{    
}

public class Orange : Fruit
{    
}

Now, you want all the apples before the oranges. Set the value and sort it.

//Suppose that this is your list fruits
var fruits = new List<Fruit>();
fruits.OfType<Apple>().ForEach(a=> a.MyOrder = 1);
fruits.OfType<Orange>().ForEach(a=> a.MyOrder = 2);
var sorted = fruits.OrderBy(x=>MyOrder);

There will be a disadvantage is if you have multifruit.

But if your order doesn't change, like apples always before oranges. Set the MyOrder in class.

    public abstract class Fruit
    {
        public abstract int MyOrder {get;}
    }

    public class Apple : Fruit
    {    
        public override int MyOrder {
            get { return 1;}
        }
    }

    public class Orange : Fruit
    {  
        public override int MyOrder {
            get { return 2;}
        }  
    }
Antoine V
  • 6,998
  • 2
  • 11
  • 34