88

I am populating an array with instances of a class:

BankAccount[] a;
. . .

a = new BankAccount[]
{
    new BankAccount("George Smith", 500m),
    new BankAccount("Sid Zimmerman", 300m)
};

Once I populate this array, I would like to sort it by balance amounts. In order to do that, I would like to be able to check whether each element is sortable using IComparable.
I need to do this using interfaces. So far I have the following code:

public interface IComparable
{
    decimal CompareTo(BankAccount obj);
}

But I'm not sure if this is the right solution. Any advice?

Green Falcon
  • 818
  • 3
  • 17
  • 48
Alex Gordon
  • 57,446
  • 287
  • 670
  • 1,062

8 Answers8

163

You should not define IComparable yourself. It is already defined. Rather, you need to implement IComparable on your BankAccount class.

Where you defined the class BankAccount, make sure it implements the IComparable interface. Then write BankAccount.CompareTo to compare the balance amounts of the two objects.

public class BankAccount : IComparable<BankAccount>
{
    [...]

    public int CompareTo(BankAccount that)
    {
        if (this.Balance <  that.Balance) return -1;
        if (this.Balance == that.Balance) return 0;
        return 1;
    }
}

Edit to show Jeffrey L Whitledge's solution from comments:

public class BankAccount : IComparable<BankAccount>
{
    [...]

    public int CompareTo(BankAccount that)
    {
        return this.Balance.CompareTo(that.Balance);
    }
}
neonblitzer
  • 124
  • 1
  • 2
  • 10
abelenky
  • 63,815
  • 23
  • 109
  • 159
  • im sorry can u give me an example of how i would implement it – Alex Gordon Nov 15 '10 at 19:33
  • 47
    I like `return this.Balance.CompareTo(that.Balance);` – Jeffrey L Whitledge Nov 15 '10 at 20:02
  • 3
    @i am a girl - I am not sure what you mean. Perhaps you are not clear on which part of the code I was replacing. I will put all of it in the comment, then: `public class BankAccount : IComparable { [...] int CompareTo(BankAccount that) { return this.Balance.CompareTo(that.Balance); } }` Is that more clear? – Jeffrey L Whitledge Nov 16 '10 at 18:28
  • 5
    Another way would be `return Balance - that.Balance;` – fbiagi Jul 25 '14 at 11:30
  • 7
    In the 1st version, it should be `this.Balance < that.Balance` to sort by balance ascending. -1 = this is less than that, 0 = this is equal to that, 1 = this is greater than that – Keith Jul 28 '14 at 22:15
  • 1
    @fbiagi Thank you for bringing this to attention. Most people think `IComparable` always returns either -1, 0 or 1, but there is nothing in the rules that says `IComparable` will return -1 or 1. The definition of `IComparable` states that it will return a number less than zero or a number more than zero, meaning it is perfectly acceptable for someone to use subtraction to implement `CompareTo`. Some people are unaware of this and start using switch statements on `IComparable.CompareTo`, which is incorrect and is liable to break on certain comparisons. – Pharap Sep 21 '14 at 04:03
  • You may also want to return 1 for a null `that` object if you feel a `null` pointer has less intrinsic value. The example above will throw on a null and I'm not sure this is desirable during sort ops. – Luke Puplett Oct 15 '14 at 13:16
  • Interface implementation cannot be public if I remember right – nicecatch Jan 12 '15 at 10:09
  • 1
    I believe you have your results backwards. If the current objects value is GREATER than the one being compared to, then it should return 1. You have it returning -1. – Jeffrey Harmon Aug 28 '15 at 14:53
  • 5
    `return Balance - that.Balance` is not a good idea if the balance ever nears the limits of its type, because overflow could give you the wrong results. For example, if Balance were a `short`, and `this.Balance` was 32700 and `that.Balance` was -100, the result of the subtraction would be -32736, when clearly the result of `CompareTo` should be a positive number. Similarly, if Balance is a `ushort`, the result of the subtraction can never be negative, which is also clearly wrong. – James May 19 '16 at 02:48
  • I fixed the mistake in the first example implementation – changed `>` to `<`. This is the first Google result for "implement icomparable", so it should probably be correct. – neonblitzer Oct 08 '20 at 14:12
  • Such a little beauty, with "this" and "that". Unfortunately, in modern times... "this" creates an IDE warning that it's unnecessary, and "that" doesn't work OOTB due to nullability... – Eike Nov 07 '22 at 11:01
  • @Eike: This answer was written 12 years ago. I'm not surprised that it is no longer ideal code. But it was good at the time it was written. – abelenky Nov 07 '22 at 15:04
  • @abelenky My problem is I feel it's just perfect code, which my analyzers nowadays won't let me go through with... (And I feel SO questions are never dead. ;) ) – Eike Nov 07 '22 at 15:18
18

IComparable already exists in .NET with this definition of CompareTo

int CompareTo(Object obj)

You are not supposed to create the interface -- you are supposed to implement it.

public class BankAccount : IComparable {

    int CompareTo(Object obj) {
           // return Less than zero if this object 
           // is less than the object specified by the CompareTo method.

           // return Zero if this object is equal to the object 
           // specified by the CompareTo method.

           // return Greater than zero if this object is greater than 
           // the object specified by the CompareTo method.
    }
}
Lou Franco
  • 87,846
  • 14
  • 132
  • 192
  • 1
    im sorry can u give me an example of how i would implement it – Alex Gordon Nov 15 '10 at 19:33
  • 2
    And what if obj is `null` or of another type than `BankAccount`? EDIT: According to MSDN here: https://msdn.microsoft.com/en-us/library/system.icomparable.compareto(v=vs.110).aspx#Anchor_1 : throw an `ArgumentException`. – Ray Apr 21 '18 at 12:53
17

Do you want to destructively sort the array? That is, do you want to actually change the order of the items in the array? Or do you just want a list of the items in a particular order, without destroying the original order?

I would suggest that it is almost always better to do the latter. Consider using LINQ for a non-destructive ordering. (And consider using a more meaningful variable name than "a".)

BankAccount[] bankAccounts = { whatever };
var sortedByBalance = from bankAccount in bankAccounts 
                      orderby bankAccount.Balance 
                      select bankAccount;
Display(sortedByBalance);
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • i want to destruct it implementing icompare – Alex Gordon Nov 15 '10 at 19:34
  • 8
    @Lippert: While this is a very valid response, it seems from the discussion that the OP barely understands what it means to implement an interface. She's probably not yet ready for the level of questions you're asking. – abelenky Nov 15 '10 at 19:47
  • Hi Eric, what's the best practice for dealing with `null` when implementing `IComparable` and subclassing `Comparer` assuming `T` is a reference type? Does it depend on the user case or it's normally better to throw exception since the real comparison logic is often forwarded to some property on `T`. – dragonfly02 Feb 04 '17 at 20:51
  • 1
    @stt106: That sounds like a question; consider posting it as a question. Short answer: I would implement a total order on all possible values, including null. Traditionally null is smaller than all other possibilities. That said, it might be sensible to throw an exception if you think that it is always *wrong* for a null to be provided. – Eric Lippert Feb 05 '17 at 00:48
11

An alternative is to use LINQ and skip implementing IComparable altogether:

BankAccount[] sorted = a.OrderBy(ba => ba.Balance).ToArray();
Matt Greer
  • 60,826
  • 17
  • 123
  • 123
8

There is already IComparable<T>, but you should ideally support both IComparable<T> and IComparable. Using the inbuilt Comparer<T>.Default is generally an easier option. Array.Sort, for example, will accept such a comparer.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
2

If you only need to sort these BankAccounts, use LINQ like following

BankAccount[] a = new BankAccount[]
{
    new BankAccount("George Smith", 500m),
    new BankAccount("Sid Zimmerman", 300m)
};

a = a.OrderBy(bank => bank.Balance).ToArray();
Zain Shaikh
  • 6,013
  • 6
  • 41
  • 66
2

If you need to compare multiple fields, you can get some help from the compiler by using the new tuple syntax:

public int CompareTo(BankAccount other) =>
  (Name, Balance).CompareTo(
    (other.Name, other.Balance));

This scales to any number of properties, and it will compare them one-by-one as you would expect, saving you from having to implement many if-statements.

Note that you can use this tuple syntax to implement other members as well, for example GetHashCode. Just construct the tuple and call GetHashCode on it.

Daniel Lidström
  • 9,930
  • 1
  • 27
  • 35
0

This is an example to the multiple fields solution provided by @Daniel Lidström by using tuple:

   public static void Main1()
        {
            BankAccount[] accounts = new BankAccount[]
            {
        new BankAccount()
        {
            Name = "Jack", Balance =150.08M
        }, new BankAccount()
        {
            Name = "James",Balance =70.45M
        }, new BankAccount()
        {
            Name = "Mary",Balance =200.01M
        }, new BankAccount()
        {
            Name = "John",Balance =200.01M
        }};
            Array.Sort(accounts);
            Array.ForEach(accounts, x => Console.WriteLine($"{x.Name} {x.Balance}"));
        }

    }
    public class BankAccount : IComparable<BankAccount>
    {
        public string Name { get; set; }
        
        public int Balance { get; set; }         

        public int CompareTo(BankAccount other) =>
           (Balance,Name).CompareTo(
               (other.Balance,other.Name ));

    }


Try it

M.Hassan
  • 10,282
  • 5
  • 65
  • 84