In this article, we are going to look at different ways to sort a List by a property in the object.
Let’s dive in.
Creating a Demo Model
Let’s begin by creating a Book
class we are going to work with:
public class Book { public string Title { get; set; } public string Author { get; set; } public int Pages { get; set; } }
We can sort List<Book>
by any property in the Book
class.
To verify the functionality, let’s create some sample data:
private static readonly Book gatsby = new Book { Title = "The Great Gatsby", Author = "F. Scott Fitzgerald", Pages = 279 }; private static readonly Book lotr = new Book { Title = "The Lord Of The Rings", Author = "J.R.R. Tolkien", Pages = 1216 }; private static readonly Book pride = new Book { Title = "Pride & Prejudice", Author = "Jane Austen", Pages = 329 }; private static readonly Book emma = new Book { Title = "Emma", Author = "Jane Austen", Pages = 1036 };
Let’s create a CreateBookData()
method to keep these Book
objects in an unsorted List<Book>
:
private static void CreateBookData() { _books = new List<Book> { gatsby, lotr, pride, emma }; }
And, let’s create another CreateBookDataSortedByTitle()
method to contain a sorted List<Book>
by the Title
property:
private static void CreateBookDataSortedByTitle() { _booksSortedTitle = new List<Book> { emma, pride, gatsby, lotr }; }
Using the OrderBy() Method in LINQ
The most common way of sorting lists is by using the OrderBy()
LINQ method to create a new, sorted copy of the original list. This creates a new list with the elements sorted by using a key. This key should be a property of the object.
Let’s look at a scenario to sort the List<Book>
by the Title
property by creating a method:
public List<Book> SortByTitleUsingLinq(List<Book> originalList) { return originalList.OrderBy(x => x.Title).ToList(); }
This method takes a List<Book>
as argument and returns a new List<Book>
that’s sorted according to the Title
property.
Now, on executing the method, we get a List<Book>
sorted by Title
:
var sort = new Sort(); var sortedList = sort.SortByTitleUsingLinq(_books); CollectionAssert.AreEqual(_booksSortedTitle, sortedList);
As we can see now the presorted list is equal to our sorted list.
Sorting by Multiple Properties
So, we have looked at sorting a list of objects by using one property. However, we can also sort the list by using multiple properties.Â
We often do something similar in SQL with an ORDER BY clause:
ORDER BY Author, Pages
Let’s say we want the same list to be sorted by Author
and Pages
properties.
We can do so by using ThenBy()
method in addition to OrderBy()
:
public List<Book> SortByAuthorAndPagesUsingLinq(List<Book> originalList) { return originalList.OrderBy(x => x.Author).ThenBy(x => x.Pages).ToList(); }
This method returns a new List<Book>
sorted, first by the Author
property and then by the Pages
property.
Let’s create another sample sorted list:
private static void CreateBookDataSortedByAuthorPages() { _booksSortedAuthorPages = new List<Book> { gatsby, lotr, pride, emma }; }
This list contains Book objects sorted by multiple properties. We can verify this by executing the method:
var sort = new Sort(); var sortedList = sort.SortByAuthorAndPagesUsingLinq(_books); CollectionAssert.AreEqual(_booksSortedAuthorPages, sortedList);
Using the IComparable<T> Interface
The IComparable<T>
interface allows us to sort a list of complex types without using LINQ.Â
To do that, we need to implement the IComparable
interface in the Book
class and implement the CompareTo()
method. This CompareTo()
method returns an integer whose value can be:
- Less than zero – meaning the compared instance comes after the current instance in the sort order
- Zero – meaning both the compared and the current instances have the same position in the sort order
- Greater than zero – meaning the compared instance comes before the current instance in the sort order
Let’s modify the Book
class to implement the IComparable
interface:
public class Book : IComparable<Book> { public string Title { get; set; } public string Author { get; set; } public int Pages { get; set; } public int CompareTo(Book other) { if (other == null) return 1; if (this.Pages > other.Pages) return 1; else if (this.Pages < other.Pages) return -1; else return 0; } }
Now, the Book class can invoke the Sort()
method provided in the List<T>
class to order the objects according to Pages
property.
Let’s create another sample List<Book>
sorted by Pages
to verify this functionality:
private static void CreateBookDataSortedByPages() { _booksSortedPages = new List<Book> { gatsby, pride, emma, lotr }; }
When we invoke the Sort()
method on the original unsorted List<Book>
, it is sorted without needing to create a separate copy of the original list:
_books.Sort(); CollectionAssert.AreEqual(_booksSortedPages, _books);
Using the IComparer<T> Interface
We can also use IComparer<T>
interface to have our comparison implementations and hence, sort the list according to our custom implementations.
The IComparer<T>
and IComparable<T>
interfaces work differently.
We use IComparable<T>
for intrinsic comparison, i.e. comparing another instance to the current instance.IComparer<T>
, on the other hand, provides us with a mechanism to compare two different objects.
Let’s implement the IComparer
to sort the List<Book>
by Title
:
public int Compare(Book x, Book y) { if (x == null) return -1; if (y == null) return 1; return x.Title.CompareTo(y.Title); }
Now, we need to pass an instance of the SortBookByTitle
class as an argument to the Sort()
method:
_books.Sort(new SortBookByTitle()); CollectionAssert.AreEqual(_booksSortedTitle, _books);
IComaparer<T>
sorts the list in place and doesn’t create a new copy of the original list.
Using the Comparison<T> Delegate
An overload of the Sort()
method expects Comparison<T>
delegate as an argument. We can use this in addition to the other approaches to sort the list.
Let’s start by creating a method that matches the signature of the Comparison
delegate:
public static int CompareBooks(Book x, Book y) { return x.Title.CompareTo(y.Title); }
We can then create an instance of the Comparison delegate and pass CompareBooks
as an argument. Hence, the delegate points to our new method.
If we pass this delegate instance as an argument in the Sort()
method, we can sort the List<Book>
by Title
:
var bookComparer = new Comparison<Book>(Sort.CompareBooks); _books.Sort(bookComparer); CollectionAssert.AreEqual(_booksSortedTitle, _books);
Using lambda expressions, we can further simplify this approach:
_books.Sort((x, y) => x.Title.CompareTo(y.Title)); CollectionAssert.AreEqual(_booksSortedTitle, _books);
So, with lambda expressions, we don’t need to declare a new comparison method or invoke the instance of the Comparison
delegate any longer.
Performance of Different Sorting Methods
Let’s see how these different ways of sorting a list of objects by a property perform against each other.
First, let’s write a method to generate a random number that we will use as the value for the PageNumber
property:
public static int GenerateNumber(int min, int max) { return _random.Next(min, max); }
The method GenerateNumber()
takes two integer values as input. Using the Random
class, we generate a number between these integers.
Now, let’s write a method to generate a random string that we will use as the value for Title
 and Author
properties:
public static string GenerateString(int size) { var builder = new StringBuilder(size); char start = 'a'; for (int i = 0; i < size; i++) { var text = (char)_random.Next(start, start + 26); builder.Append(text); } return builder.ToString(); }
To keep it simple, we will generate lower-case text of a specified size.
Next, let’s create a collection that holds a list of Book
objects with randomly assigned property values:
public IEnumerable<List<Book>> GenerateBooks() { var data = new List<List<Book>>(); var books = new List<Book>(); for (int i = 0; i < 1000; i++) { var book = new Book { Title = DataGenerator.GenerateString(10), Author = DataGenerator.GenerateString(15), Pages = DataGenerator.GenerateNumber(100, 1000) }; books.Add(book); } data.Add(books); return data; }
We will simulate the sorting scenario on 1000 books. These Book objects contain the property values generated from GenerateNumber()
and GenerateString()
methods.
Let’s assess the performance results of the sorting methods:
| Method | Mean | Error | StdDev | |------------------------------ |----------:|---------:|---------:| | SortByTitleUsingLinq | 371.55 us | 3.830 us | 3.395 us | | SortByTitleIComparable | 22.79 us | 0.108 us | 0.101 us | | SortByTitleIComparer | 246.08 us | 0.533 us | 0.445 us | | SortByTitleComparisonDelegate | 249.68 us | 2.495 us | 2.212 us |
We can see that the intrinsic sorting using IComparable
implementation is the fastest way to sort a list of objects by far, taking 22.79 us. Whereas, using LINQ takes the longest with 371.55 us.
Conclusion
In this article, we learned about sorting a list based on the property of an object. We saw different ways of doing so, using the LINQ OrderBy()
method, implementation of IComparable
and IComparer
interfaces, and using the Comparison
delegate.