banana
banana

Reputation: 37

Sorting a list using reflection to pass a member name by string

I have a and mini class and a List<T> thereof.

List<mini> result;

public class mini
{
    public long SN;
    public int PlayTime;
    public string Date;
    public int Score;
}

//The value is the Name of SN, PlayTime, Date, Score
string sortColumn;      
string sortColumnDir;  //asc, desc value

The caller can specify a member name to sort on, and I try to sort on the result value:

if("SN" == sortColumn)
    var sortresult = result.OrderBy(c => c.SN).ToList<miniCompletion>();
else if("PlayTime" == Date)
    var sortresult = result.OrderBy(c => c.PlayTime).ToList<miniCompletion>();
else if("PlayTime" == sortColumn)
    var sortresult = result.OrderBy(c => c.PlayTime).ToList<miniCompletion>();
else if("Score" == sortColumn)
    var sortresult = result.OrderBy(c => c.Score).ToList<miniCompletion>();

But this code is too inefficient, because it involves a lot of copy-pasting nearly duplicate code. And for sorting descendingly, the code doubles in size.

So I tried:

var sortresult = result.OrderBy(c => c.GetType().GetMember(sortColumn)[0].Name).ToList();

But the sort failed.

Upvotes: 1

Views: 461

Answers (3)

Aleks Andreev
Aleks Andreev

Reputation: 7054

You can build your sorting lambda dynamically with linq expressions. Try this code:

var parameter = Expression.Parameter(typeof(mini), "c");
var member = Expression.PropertyOrField(parameter, sortColumn);
var cast = Expression.Convert(member, typeof(IComparable));
var lambda = Expression.Lambda<Func<mini, IComparable>>(cast, parameter);

now lambda is an expression like c => (IComparable)c.Date and can be compiled to Func<mini, IComparable>:

var func = lambda.Compile();

At this moment you can sort your result:

var sortedResult = sortColumnDir == "ASC"
    ? result.OrderBy(func)
    : result.OrderByDescending(func);

You can find demo here

Please note, that this code is working because all fields in mini are implementing IComparable interface

Upvotes: 0

Noel Santos
Noel Santos

Reputation: 17

Change this:

if("SN" == sortColumn) var sortresult = result.OrderBy(c => c.SN).ToList<miniCompletion>(); else if("PlayTime" == Date) var sortresult = result.OrderBy(c => c.PlayTime).ToList<miniCompletion>(); else if("PlayTime" == sortColumn) var sortresult = result.OrderBy(c => c.PlayTime).ToList<miniCompletion>(); else if("Score" == sortColumn) var sortresult = result.OrderBy(c => c.Score).ToList<miniCompletion>();

With enums for asc, desc and sortColumn like:

If(SortComun.SN == sortColumn)

Int comparison is faster and enums are cleaner. Also, you can try result.AsParallel().OrderBy() if you have too much registers.

About your second approach, try:

var sortResult = result.OrderBy(c => c.GetType().GetProperty(sortColumn).GetValue(c)).ToList();

You can test which approach suits you better. Having reflection as the most generic answer, the other one could be faster in an intensive environment.

If you test and share your tests result could be awesome.

Upvotes: 0

CodeCaster
CodeCaster

Reputation: 151654

The sort fails because you're sorting by the property name, not its value. The name is of course equal for all items.

You need to get the value. Use GetField(...).GetValue(c). If you can't use GetField() (or you'd rather even use properties, not fields, so GetProperty()), see How do I get the value of MemberInfo?.

Upvotes: 2

Related Questions