C# – Generic Sorting of Lists

cgeneric-programming

I have several Lists<> that holds objects of different classes.

List<classA> listA;
List<classB> listB;
List<classC> listC;
//...
List<classM> listM;

The classes do not relate in any way and have nothing in common except that they might have a single property marked with an Attribute SortBy=true

    [CustomObjectComparerAttribute(IncludeInCompare = true, Title = "View Order", SortBy=true)]
    public int ViewOrder { get; set; }

What I would like to do is to have a generic function that accepts a list of any sort. This is problem Nr 1, because I can not figure out a way to make a function accept Lists<> of different types.

private List<object> SortList(List<object> aList)  //<-- this is not allowed (problem 1)
{
    //Get which property we sort by...
    PropertyInfo  prop = GetPropertyToSortBy(objectA);

    //Sort the list
    //Problem Nr 2: How do I get `PropertyInfo prop` in the statement below
    List<Order> SortedList = aList.OrderBy(o=>o.ThePropToSortBy).ToList();
}
    private PropertyInfo GetPropertyToSortBy(object o)
    {
        Type t = o.GetType();
        foreach (PropertyInfo prop in t.GetProperties(BindingFlags.Instance | BindingFlags.Public))
        {
            if (Attribute.IsDefined(prop, typeof(CustomObjectComparerAttribute)) && ((CustomObjectComparerAttribute)Attribute.GetCustomAttribute(prop, typeof(CustomObjectComparerAttribute))).SortBy == true)
            {
                return prop;
            }
        }
        return null;
    }

I feel that Im attacking this problem from the wrong angle. Am I on the right track?, and if so, how can I solve my problems?

if not, can you point me in the right direction?

Best Answer

Your specific problems are relatively easily solved.

Problem 1 is just a syntax issue for how to write methods with generic type parameters. In your case this would be:

private List<T> SortList<T>(List<T> aList)

It's the <T> after the method name which allows you to use T as a generic type parameter in the rest of the signature

Problem 2 just requires a bit of murky digging into reflection classes. You already have a ProperyInfo instance, so you just need to use PropertyInfo.GetValue, documented here. In other words:

List<Order> SortedList = aList.OrderBy(o=>prop.GetValue(o)).ToList();

You'll also run into some secondary problems- for example, reflection is quite slow, and you're going to be using it many times while sorting. Performance often isn't a primary concern, but for something like sorting a list it's potentially important. I don't know off the top of my head if it'd actually be an issue in this case... but finding out would require worrying about it, research, and all that other stuff you'd rather just avoid altogether.

Also, this is all pretty complex! Now to do something as simple as a list, you're having to deal with attributes, reflection and all that muck.

The route cause of your problems is that you're trying to expose something that should be behavior as metadata. This is a slightly unusual variant of a more common issue that crops up in OO design: exposing something that should be behavior as data.

Fortunately, .NET already gives us a good way to let a class define how it should be sorted in the form of behavior: the IComparable<T> interface. Again, you're probably best off reading the documentation, but in your case, instead of writing:

public class SomeSortableThing
{
    [CustomObjectComparer]
    public int SortByMe {get; set;}
}

You'd write:

public class SomeSortableThing : IComparable<SomeSortableThing>
{
    public int SortByMe {get; set;}
    public int CompareTo(SomeSortableThing other)
    {
        return SortByMe.CompareTo(other.SortByMe);
    }
}

This gives us the advantages:

  • There's no need for reflection or attributes
  • It's idiomatic
  • It's easy to use- any consumer can just use the standard sorting methods without needing any special helper method/class
  • It's much more flexible. If you want to order by one property then another, you can do that easily, rather than having to invent a whole new way to represent that through attributes
  • Our sortable properties don't need to be public, they can be encapsulated if that's appropriate.
  • You can interchangeably sort on properties and fields, without needing to support that in your reflection.
Related Topic