How to Implement IComparable interface?
Asked Answered
A

8

91

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?

Austen answered 15/11, 2010 at 19:24 Comment(0)
L
168

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);
    }
}
Lowkey answered 15/11, 2010 at 19:28 Comment(14)
im sorry can u give me an example of how i would implement itAusten
I like return this.Balance.CompareTo(that.Balance);Emilioemily
@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<BankAccount> { [...] int CompareTo(BankAccount that) { return this.Balance.CompareTo(that.Balance); } } Is that more clear?Emilioemily
Another way would be return Balance - that.Balance;Kimberleykimberli
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 thatStartling
@Kimberleykimberli 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.Bulge
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.Bugeye
Interface implementation cannot be public if I remember rightMessy
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.Ronen
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.Bring
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.Movie
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...Viscus
@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.Lowkey
@Lowkey 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. ;) )Viscus
M
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.
    }
}
Maltz answered 15/11, 2010 at 19:28 Comment(2)
im sorry can u give me an example of how i would implement itAusten
And what if obj is null or of another type than BankAccount? EDIT: According to MSDN here: msdn.microsoft.com/en-us/library/… : throw an ArgumentException.Pericline
S
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);
Steeplejack answered 15/11, 2010 at 19:32 Comment(4)
i want to destruct it implementing icompareAusten
@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.Lowkey
Hi Eric, what's the best practice for dealing with null when implementing IComparable<T> and subclassing Comparer<T> 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.Lodged
@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.Steeplejack
I
11

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

BankAccount[] sorted = a.OrderBy(ba => ba.Balance).ToArray();
Infernal answered 15/11, 2010 at 19:32 Comment(0)
D
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.

Deliberate answered 15/11, 2010 at 19:27 Comment(0)
B
4

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.

Basicity answered 27/10, 2021 at 8:49 Comment(1)
Good solution for multi fields.Noreennorene
T
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();
Troglodyte answered 15/11, 2010 at 19:34 Comment(0)
N
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

Noreennorene answered 9/2, 2022 at 16:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.