Search code examples
c#sortingcomparetoicomparable

C# - sort List<SomeClass> by using custom CompareTo method


I have the following classes hierarchy:

abstract class Product : IComparable
{
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Barcode { get; set; }

    public int CompareTo(object obj)
    {
        int ret = -1;
        if (String.Compare(this.GetType().Name, obj.GetType().Name,StringComparison.Ordinal) == 0) 
            ret = 0;

        return ret;
    }
}

abstract class Book : Product
{
    public int PagesCount { get; set; }
}

class ProgrammingBook : Book
{
    public string ProgrammingLanguage { get; set; }
}

class CulinaryBook : Book
{
    public string MainIngridient { get; set; }
}

class EsotericBook : Book
{
    public int MininumAge { get; set; }
}
   abstract class Disc : Product
{
    internal enum Content
    {
        Music,
        Video,
        Software
    }

    public Content DiscContent { get; set; }
}

class CdDisc : Disc
{

}

class DvdDisc : Disc
{

}

And I trying to sort following collection by using IComparable interface method CompareTo:

 List<Product> products = new List<Product>
        {
            new DvdDisc {Name = "The lord of the rings 2",DiscContent = Disc.Content.Video,Price = 200M,Barcode = "5435443-2"},
            new CdDisc {Name = "Antonio Vivaldi: best picks",Price = 700M, DiscContent = Disc.Content.Music,Barcode = "4543765-565"},
            new CulinaryBook{Name = "Midterranian foods",MainIngridient = "Salmon",PagesCount = 436,Price = 350M,Barcode = "41457561-897"},
            new CdDisc{Name = "Windows XP", DiscContent = Disc.Content.Software, Price = 950M, Barcode = "5433668-4"},
            new EsotericBook{Name = "Russian Freemasonry 1731-2000",MininumAge = 21,PagesCount = 2100,Price = 3000M,Barcode = "6464632-876"},
            new CdDisc {Name = "The best of Mussorgsky",Price = 300M, DiscContent = Disc.Content.Music,Barcode = "5435436-567"},
            new ProgrammingBook{Name = "CLR via C#",PagesCount = 900, Price = 1110M,ProgrammingLanguage = "C#",Barcode = "5546533-2446"},
            new DvdDisc {Name = "The lord of the rings 1",DiscContent = Disc.Content.Video,Price = 200M,Barcode = "54354423-2"},
            new ProgrammingBook{Name = "ASP.NET MVC 4",PagesCount = 800,Price = 1200M,ProgrammingLanguage = "C#",Barcode = "46476573-65"},
            new EsotericBook{Name = "Russian Freemasonry in it's future and past",MininumAge =19, PagesCount = 900, Price = 2342M,Barcode = "3656353-24"},
            new CulinaryBook{Name = "Traditional Polish dishes",MainIngridient = "Red meat",PagesCount = 630,Price = 840,Barcode = "54634234-5"}
        }; products.Sort();

Output list should look like this:

1.1 Programming books
1.2 Culinary books
1.3 Esoteric books

2.1 Cd discs sorted by content
2.2 DVD discs sorted by content

My current CompareTo method does only part of the job - comparing classes by name.


Solution

  • This is a working example that will render the exact same output:

    1.1 Programming books 
    1.2 Culinary books 
    1.3 Esoteric books 
    
    2.1 Cd discs sorted by content 
    2.2 DVD discs sorted by content 
    

    enter image description here

    I also added IComparable<Product> to more easily make it comparable with other products.

     abstract class Product : IComparable<Product>
        {
            public string Name { get; set; }
            public decimal Price { get; set; }
            public string Barcode { get; set; }
            protected abstract int InternalSortOrder { get; }
            protected virtual string SortBy { get {return Name;} }
    
    
            public int CompareTo(Product obj)
            {
                var sameType = string.Compare(GetType().Name, obj.GetType().Name, StringComparison.Ordinal) == 0;
    
                var sameBaseType = GetType().BaseType != null && obj.GetType().BaseType != null &&
                                   string.Compare(GetType().BaseType.ToString(), obj.GetType().BaseType.ToString(),
                                       StringComparison.Ordinal) == 0;
    
    
                // They have the same base type, but not the same type. Order by base type first.
                if (!sameType && !sameBaseType && GetType().BaseType != null && obj.GetType().BaseType != null)
                {
                    // Order by base type first.
                    return string.Compare(GetType().BaseType.ToString(), obj.GetType().BaseType.ToString(),
                        StringComparison.Ordinal);
                }
    
                // it's the same base type (eg. book or disc)
                if (sameBaseType)
                {
                    // Order by sort order.
                    if (obj.InternalSortOrder != this.InternalSortOrder)
                    {
                        return InternalSortOrder.CompareTo(obj.InternalSortOrder);
                    }
                }
    
                if (sameType)
                {
                    // Same sort order. We sort by name.
                    return string.Compare(SortBy, obj.SortBy, StringComparison.Ordinal);
                }
    
                // Order by Type.
                return string.Compare(GetType().Name, obj.GetType().Name, StringComparison.Ordinal);
            }
    
        }
    
        abstract class Book : Product
        {
            public int PagesCount { get; set; }
        }
    
        class ProgrammingBook : Book
        {
            public string ProgrammingLanguage { get; set; }
    
            protected override int InternalSortOrder
            {
                get { return 1; }
            }
        }
    
        class CulinaryBook : Book
        {
            public string MainIngridient { get; set; }
    
            protected override int InternalSortOrder
            {
                get { return 2; }
            }
        }
    
        class EsotericBook : Book
        {
            public int MininumAge { get; set; }
    
            protected override int InternalSortOrder
            {
                get { return 3; }
            }
        }
        abstract class Disc : Product
        {
            internal enum Content
            {
                Music,
                Video,
                Software
            }
    
            protected override string SortBy
            {
                get { return DiscContent.ToString(); }
            }
    
            public Content DiscContent { get; set; }
        }
    
        class CdDisc : Disc
        {
            protected override int InternalSortOrder
            {
                get { return 1; }
            }
        }
    
        class DvdDisc : Disc
        {
            protected override int InternalSortOrder
            {
                get { return 2; }
            }
        }
    

    UPDATE:

    I've added virtual SortByin Product. It will go to Name by default, but Disc will return Content.