NeuroAgent

How to Sort List<T> by Object Property in C#

Learn how to efficiently sort List<T> by object properties in C# using LINQ's OrderBy methods. Complete guide with code examples for sorting by date, ID, and custom criteria.

Question

How to Sort a List by Object Property in C#

I have a class called Order which has properties such as OrderId, OrderDate, Quantity, and Total. I have a list of this Order class:

csharp
List<Order> objListOrder = new List<Order>();
GetOrderList(objListOrder); // fill list of orders

I want to sort the list based on one property of the Order object; for example, either by OrderDate or OrderID.

How can I do this in C#?

NeuroAgent

You can sort a List by object property in C# using LINQ’s OrderBy method. For your Order class, you can sort by OrderDate using list.OrderBy(x => x.OrderDate) or by OrderID using list.OrderBy(x => x.OrderID). This approach is efficient and maintains the original list while returning a new sorted sequence.

Basic LINQ Sorting Methods

The most common way to sort a List by object property is using LINQ’s OrderBy method. This method returns a new sorted sequence without modifying the original list.

Using OrderBy for Ascending Sort

csharp
// Sort by OrderDate (ascending)
List<Order> sortedByDate = objListOrder.OrderBy(order => order.OrderDate).ToList();

// Sort by OrderID (ascending) 
List<Order> sortedById = objListOrder.OrderBy(order => order.OrderID).ToList();

// Sort by Quantity (ascending)
List<Order> sortedByQuantity = objListOrder.OrderBy(order => order.Quantity).ToList();

Using OrderByDescending for Descending Sort

csharp
// Sort by OrderDate (descending - newest first)
List<Order> sortedByDateDesc = objListOrder.OrderByDescending(order => order.OrderDate).ToList();

// Sort by OrderID (descending - highest ID first)
List<Order> sortedByIdDesc = objListOrder.OrderByDescending(order => order.OrderID).ToList();

The OrderBy method uses a lambda expression that specifies which property to sort by. The lambda parameter represents each element in the collection, and you access the property to sort by.

Sorting by Different Properties

You can create a flexible sorting method that accepts the property as a parameter using generics and expression trees, or simply use different LINQ queries for different sorting needs.

Dynamic Property Sorting

csharp
public static List<T> SortByProperty<T>(List<T> list, string propertyName, bool ascending = true)
{
    var param = Expression.Parameter(typeof(T), "x");
    var property = Expression.Property(param, propertyName);
    var lambda = Expression.Lambda<Func<T, object>>(Expression.Convert(property, typeof(object)), param);
    
    return ascending 
        ? list.AsQueryable().OrderBy(lambda).ToList()
        : list.AsQueryable().OrderByDescending(lambda).ToList();
}

// Usage:
List<Order> sortedByDate = SortByProperty(objListOrder, "OrderDate");
List<Order> sortedById = SortByProperty(objListOrder, "OrderID", false); // descending

Property Type Considerations

Different properties may require different comparison approaches:

csharp
// For dates (DateTime properties)
List<Order> sortedByDate = objListOrder
    .OrderBy(order => order.OrderDate)
    .ToList();

// For numeric properties (int, double, decimal, etc.)
List<Order> sortedByQuantity = objListOrder
    .OrderBy(order => order.Quantity)
    .ToList();

// For string properties
List<Order> sortedByCustomerName = objListOrder
    .OrderBy(order => order.CustomerName)
    .ToList();

// For custom objects (requires IComparable or comparer)
List<Order> sortedByCustomer = objListOrder
    .OrderBy(order => order.Customer)
    .ToList();

Custom Comparers for Complex Sorting

When you need more complex sorting logic, you can implement custom comparers or use the ThenBy method for chaining multiple sort criteria.

Implementing IComparer

csharp
public class OrderDateComparer : IComparer<Order>
{
    public int Compare(Order x, Order y)
    {
        if (x == null && y == null) return 0;
        if (x == null) return -1;
        if (y == null) return 1;
        
        return DateTime.Compare(x.OrderDate, y.OrderDate);
    }
}

// Usage:
List<Order> sortedByDate = objListOrder
    .OrderBy(new OrderDateComparer())
    .ToList();

Using Lambda Expressions for Custom Logic

csharp
// Sort by month first, then by day
List<Order> sortedByMonthAndDay = objListOrder
    .OrderBy(order => order.OrderDate.Month)
    .ThenBy(order => order.OrderDate.Day)
    .ToList();

// Sort by total amount, then by quantity
List<Order> sortedByTotalAndQuantity = objListOrder
    .OrderBy(order => order.Total)
    .ThenBy(order => order.Quantity)
    .ToList();

// Custom sorting logic (e.g., sort by whether order is urgent)
List<Order> sortedByPriority = objListOrder
    .OrderBy(order => order.OrderDate < DateTime.Now.AddDays(7)) // urgent orders first
    .ThenBy(order => order.OrderDate)
    .ToList();

Multiple Property Sorting

LINQ provides ThenBy and ThenByDescending methods for secondary sorting criteria when the primary sort results in ties.

Chain Sorting with ThenBy

csharp
// Sort by customer name, then by order date
List<Order> sortedByNameAndDate = objListOrder
    .OrderBy(order => order.CustomerName)
    .ThenBy(order => order.OrderDate)
    .ToList();

// Sort by total (descending), then by quantity (ascending)
List<Order> sortedByTotalAndQuantity = objListOrder
    .OrderByDescending(order => order.Total)
    .ThenBy(order => order.Quantity)
    .ToList();

// Sort by year, month, and day
List<Order> sortedByYearMonthDay = objListOrder
    .OrderBy(order => order.OrderDate.Year)
    .ThenBy(order => order.OrderDate.Month)
    .ThenBy(order => order.OrderDate.Day)
    .ToList();

Multiple Sorting Scenarios

csharp
// Scenario 1: Sort by priority (urgent orders first), then by date
List<Order> sortedByPriority = objListOrder
    .OrderBy(order => order.OrderDate < DateTime.Now.AddDays(3))
    .ThenBy(order => order.OrderDate)
    .ToList();

// Scenario 2: Sort by customer type, then by order value
List<Order> sortedByCustomerAndValue = objListOrder
    .OrderBy(order => order.Customer.CustomerType)
    .ThenByDescending(order => order.Total)
    .ToList();

// Scenario 3: Sort by region, then by sales representative, then by date
List<Order> sortedByRegionAndRep = objListOrder
    .OrderBy(order => order.Region)
    .ThenBy(order => order.SalesRep.Name)
    .ThenBy(order => order.OrderDate)
    .ToList();

Performance Considerations

When sorting large collections, performance becomes important. Here are some key considerations:

In-Place Sorting vs New Collections

csharp
// Method 1: Create new sorted list (original preserved)
List<Order> sortedList = objListOrder.OrderBy(x => x.OrderDate).ToList();

// Method 2: Sort in-place (modifies original list)
objListOrder.Sort((x, y) => x.OrderDate.CompareTo(y.OrderDate));

// Method 3: Use List<T>.Sort with custom comparison
objListOrder.Sort((x, y) => x.OrderDate.CompareTo(y.OrderDate));

Performance Comparison

Method Memory Usage Performance Original List Modified When to Use
OrderBy().ToList() High (creates new list) Good No When you need to preserve original order
List<T>.Sort() Low (in-place) Excellent Yes When you want to modify the original list
OrderByDescending() High Good No For descending order requirements

Optimizing Large Collections

csharp
// For very large collections, consider:
List<Order> sortedList = objListOrder
    .AsParallel()  // Use parallel processing
    .OrderBy(order => order.OrderDate)
    .ToList();

// Or for specific scenarios:
List<Order> sortedList = objListOrder
    .OrderBy(order => order.OrderDate, new DateComparer())
    .ToList();  // Custom comparer can be optimized

Complete Code Examples

Here’s a complete example with the Order class and various sorting implementations:

Order Class Definition

csharp
public class Order
{
    public int OrderID { get; set; }
    public DateTime OrderDate { get; set; }
    public int Quantity { get; set; }
    public decimal Total { get; set; }
    public string CustomerName { get; set; }
    public string CustomerType { get; set; }
    public string Region { get; set; }
    
    // Additional properties for more complex examples
    public bool IsUrgent { get; set; }
    public int Priority { get; set; }
    
    // Constructor for easy object creation
    public Order(int id, DateTime date, int qty, decimal total, string customer)
    {
        OrderID = id;
        OrderDate = date;
        Quantity = qty;
        Total = total;
        CustomerName = customer;
        IsUrgent = OrderDate < DateTime.Now.AddDays(3);
        Priority = IsUrgent ? 1 : 2;
    }
}

Complete Sorting Implementation

csharp
using System;
using System.Collections.Generic;
using System.Linq;

public class OrderSortingExamples
{
    public static void Main()
    {
        // Create sample data
        List<Order> orders = new List<Order>
        {
            new Order(3, DateTime.Now.AddDays(-2), 5, 150.00m, "Customer A"),
            new Order(1, DateTime.Now.AddDays(-5), 3, 75.50m, "Customer B"),
            new Order(2, DateTime.Now.AddDays(-1), 8, 200.00m, "Customer A"),
            new Order(4, DateTime.Now.AddDays(-3), 2, 50.25m, "Customer C"),
            new Order(5, DateTime.Now.AddDays(-4), 6, 125.75m, "Customer B")
        };
        
        // Example 1: Sort by OrderDate (ascending)
        List<Order> sortedByDate = orders.OrderBy(o => o.OrderDate).ToList();
        Console.WriteLine("Sorted by Date (Ascending):");
        PrintOrders(sortedByDate);
        
        // Example 2: Sort by OrderID (ascending)
        List<Order> sortedById = orders.OrderBy(o => o.OrderID).ToList();
        Console.WriteLine("\nSorted by OrderID (Ascending):");
        PrintOrders(sortedById);
        
        // Example 3: Sort by Total (descending)
        List<Order> sortedByTotal = orders.OrderByDescending(o => o.Total).ToList();
        Console.WriteLine("\nSorted by Total (Descending):");
        PrintOrders(sortedByTotal);
        
        // Example 4: Multiple sorting - Customer name, then by date
        List<Order> sortedByNameAndDate = orders
            .OrderBy(o => o.CustomerName)
            .ThenBy(o => o.OrderDate)
            .ToList();
        Console.WriteLine("\nSorted by Customer Name and Date:");
        PrintOrders(sortedByNameAndDate);
        
        // Example 5: Priority sorting (urgent orders first)
        List<Order> sortedByPriority = orders
            .OrderBy(o => o.IsUrgent)
            .ThenBy(o => o.OrderDate)
            .ToList();
        Console.WriteLine("\nSorted by Priority (Urgent First):");
        PrintOrders(sortedByPriority);
        
        // Example 6: In-place sorting
        orders.Sort((x, y) => x.Quantity.CompareTo(y.Quantity));
        Console.WriteLine("\nSorted by Quantity (In-Place):");
        PrintOrders(orders);
    }
    
    private static void PrintOrders(List<Order> orders)
    {
        foreach (var order in orders)
        {
            Console.WriteLine($"ID: {order.OrderID}, Date: {order.OrderDate:d}, " +
                            $"Qty: {order.Quantity}, Total: {order.Total:C}, " +
                            $"Customer: {order.CustomerName}");
        }
    }
}

Advanced Sorting Techniques

csharp
public static class OrderSortingExtensions
{
    // Extension method for sorting by property name
    public static List<T> SortBy<T>(this List<T> list, string propertyName, bool ascending = true)
    {
        var param = Expression.Parameter(typeof(T), "x");
        var property = Expression.Property(param, propertyName);
        var lambda = Expression.Lambda<Func<T, object>>(Expression.Convert(property, typeof(object)), param);
        
        return ascending 
            ? list.AsQueryable().OrderBy(lambda).ToList()
            : list.AsQueryable().OrderByDescending(lambda).ToList();
    }
    
    // Extension method for multiple property sorting
    public static List<T> SortByMultiple<T>(this List<T> list, params (string Property, bool Ascending)[] sortCriteria)
    {
        IQueryable<T> query = list.AsQueryable();
        
        foreach (var criteria in sortCriteria)
        {
            var param = Expression.Parameter(typeof(T), "x");
            var property = Expression.Property(param, criteria.Property);
            var lambda = Expression.Lambda<Func<T, object>>(Expression.Convert(property, typeof(object)), param);
            
            query = criteria.Ascending 
                ? query.OrderBy(lambda)
                : query.OrderByDescending(lambda);
        }
        
        return query.ToList();
    }
}

// Usage examples:
List<Order> orders = GetOrders();

// Using extension method for single property
List<Order> sortedByDate = orders.SortBy("OrderDate");

// Using extension method for multiple properties
List<Order> sortedComplex = orders.SortByMultiple(
    ("CustomerName", true),
    ("OrderDate", false),
    ("Total", true)
);

Best Practices

When sorting List by object properties in C#, follow these best practices:

Choosing the Right Sorting Method

  • Use OrderBy when you need to preserve the original list and work with sorted data
  • Use List<T>.Sort() when you want to modify the original list in-place for memory efficiency
  • Use ThenBy when you need secondary sorting criteria
  • Use custom comparers when you have complex sorting logic or need to sort by multiple properties frequently

Performance Optimization

csharp
// Cache sorted results if used multiple times
private List<Order> _ordersByDate;
public List<Order> OrdersByDate 
{
    get 
    {
        if (_ordersByDate == null)
        {
            _ordersByDate = _allOrders.OrderBy(o => o.OrderDate).ToList();
        }
        return _ordersByDate;
    }
}

// For large collections, consider:
// 1. Parallel processing for CPU-bound operations
// 2. Asynchronous sorting for UI responsiveness
// 3. Caching sorted results

Error Handling and Edge Cases

csharp
// Handle null values in sorting
List<Order> sortedSafe = orders
    .OrderBy(o => o.OrderDate ?? DateTime.MinValue)
    .ToList();

// Handle different property types gracefully
public static List<T> SafeSort<T>(List<T> list, Func<T, IComparable> keySelector, bool ascending = true)
{
    return ascending 
        ? list.OrderBy(keySelector).Cast<T>().ToList()
        : list.OrderByDescending(keySelector).Cast<T>().ToList();
}

// Usage:
List<Order> sorted = SafeSort(orders, o => o.OrderDate as IComparable);

Maintainable Sorting Code

csharp
// Use enums for common sorting criteria
public enum OrderSortCriteria
{
    ByDate,
    ById,
    ByTotal,
    ByCustomer,
    ByPriority
}

public static List<Order> SortOrders(List<Order> orders, OrderSortCriteria criteria, bool ascending = true)
{
    switch (criteria)
    {
        case OrderSortCriteria.ByDate:
            return ascending 
                ? orders.OrderBy(o => o.OrderDate).ToList()
                : orders.OrderByDescending(o => o.OrderDate).ToList();
                
        case OrderSortCriteria.ById:
            return ascending 
                ? orders.OrderBy(o => o.OrderID).ToList()
                : orders.OrderByDescending(o => o.OrderID).ToList();
                
        case OrderSortCriteria.ByTotal:
            return ascending 
                ? orders.OrderBy(o => o.Total).ToList()
                : orders.OrderByDescending(o => o.Total).ToList();
                
        default:
            return orders;
    }
}

Testing Your Sorting Implementation

csharp
// Unit testing example
using Xunit;

public class OrderSortingTests
{
    [Fact]
    public void SortByDate_ShouldOrdersByAscendingDate()
    {
        // Arrange
        var orders = new List<Order>
        {
            new Order(1, DateTime.Now.AddDays(2), 1, 10m, "A"),
            new Order(2, DateTime.Now.AddDays(1), 1, 10m, "A"),
            new Order(3, DateTime.Now.AddDays(3), 1, 10m, "A")
        };
        
        // Act
        var sorted = orders.OrderBy(o => o.OrderDate).ToList();
        
        // Assert
        Assert.Equal(2, sorted[0].OrderID);
        Assert.Equal(1, sorted[1].OrderID);
        Assert.Equal(3, sorted[2].OrderID);
    }
    
    [Fact]
    public void SortByTotalDesc_ShouldOrderHighestFirst()
    {
        // Arrange
        var orders = new List<Order>
        {
            new Order(1, DateTime.Now, 1, 10m, "A"),
            new Order(2, DateTime.Now, 1, 50m, "A"),
            new Order(3, DateTime.Now, 1, 25m, "A")
        };
        
        // Act
        var sorted = orders.OrderByDescending(o => o.Total).ToList();
        
        // Assert
        Assert.Equal(50m, sorted[0].Total);
        Assert.Equal(25m, sorted[1].Total);
        Assert.Equal(10m, sorted[2].Total);
    }
}

Sources

  1. Microsoft Docs - LINQ Query Syntax vs Method Syntax
  2. Microsoft Docs - OrderBy Method
  3. Microsoft Docs - List.Sort Method
  4. Microsoft Docs - IComparer Interface
  5. Stack Overflow - How to sort a list of objects by a property

Conclusion

Sorting a List by object property in C# is straightforward using LINQ’s OrderBy and OrderByDescending methods. For your Order class, you can sort by OrderDate with objListOrder.OrderBy(order => order.OrderDate) or by OrderID with objListOrder.OrderBy(order => order.OrderID).

Key takeaways:

  • Use OrderBy() for ascending and OrderByDescending() for descending order
  • Chain multiple sort criteria with ThenBy() and ThenByDescending()
  • Consider in-place List<T>.Sort() for memory efficiency with large collections
  • Implement custom comparers for complex sorting logic
  • Use extension methods for more maintainable and reusable sorting code

For most scenarios, the LINQ approach provides the best balance of readability, performance, and flexibility. Choose the method that best fits your specific requirements regarding memory usage, performance needs, and whether you need to preserve the original list order.