Search code examples
c#.netlinqkeyedcollection

Creating a LINQ expression to get the correct property at the bottom of a KeyedCollection


I'm having some issues refactoring and ordering a list.

I have a class called DomainCollection which inherits from KeyedCollection.

public class DomainCollection<T> : KeyedCollection<ID, T>, IDomainCollection<T> where T : class, IDomainObject

I created and filled a list called orders.

var orders = new DomainCollection<Order>();

An order contains an OrderType which contains its own OrderTypes. So if we were to have a condition where we can perform some actions, it would look like this

if(order.Type.Type.Equals(OrderTypes.InvestmentToBankY))
{
  //Currently: logic where it creates a new list and adds the list to the existing order 
  //list and the very end. 
}

Onto my problem. I would like to be able to sort my order list so that my orders with InvestmentToBankY will be either at the bottom or top of my DomainCollection.


Solution

  • If you just want to iterate over the elements having orders with InvestmentToBankY at the top, you can just use something like:

    // Add .ToList() if you want to materialize the items immediately.
    var orderedOrders = 
        orders.OrderBy(o => o.Type.Type.Equals(OrderTypes.InvestmentToBankY));
    foreach (var order in orderedOrders)
    {
        // This will follow the desired order.
    }
    

    If you want to end up with a sorted DomainCollection, a not-so-efficient way to do so would be to reconstruct the DomainCollection using the new order. Something like the following should work:

    public class DomainCollection<T> : KeyedCollection<ID, T>, IDomainCollection<T> 
        where T : class, IDomainObject
    {
        public DomainCollection() { }
    
        public DomainCollection(IEnumerable<T> items)
        {
            foreach (var item in items)
                this.Add(item);
        }
    
        protected override ID GetKeyForItem(T item)
        {
            // TODO: implement GetKeyForItem.
            throw new NotImplementedException();
        }
    }
    

    Then, you can use it as follows:

    var orderedOrders = 
        orders.OrderBy(o => o.Type.Type.Equals(OrderTypes.InvestmentToBankY));
    
    orders = new DomainCollection(orderedOrders);
    

    A better solution that avoids creating a new collection is to rely on the fact that KeyedCollection inherits Collection<T> whose Items property is internally a List<T> and write our own sorting method that uses either a Comparison<T> or an IComparer<T> and calls the corresponding List<T>.Sort overload. Here's an example of the latter:

    public class DomainCollection<T> : KeyedCollection<ID, T>, IDomainCollection<T> 
        where T : class, IDomainObject
    {
        protected override ID GetKeyForItem(T item)
        {
            // TODO: implement GetKeyForItem.
            throw new NotImplementedException();
        }
    
        public void Sort(IComparer<T> comparer)
        {
            ((List<T>)this.Items).Sort(comparer);
        }
    }
    
    public class OrderComparer : IComparer<Order>
    {
        public int Compare(Order x, Order y)
        {
            return x.Type.Type.Equals(OrderTypes.InvestmentToBankY).
                CompareTo(y.Type.Type.Equals(OrderTypes.InvestmentToBankY));
        }
    }
    

    Usage:

    orders.Sort(new OrderComparer());