I have code to generate sorting functions for any type at run-time using expression trees. The example below sorts by public int or string properties, but is easily extended to include properties of other types. Generated sorting functions are approximately 4x slower than the equivalent function written by hand. Execution time vs the hand coded version was compared using benchmarkdotnet. How could the generation code be changed to generate faster sort functions?
public class SortBy
{
public bool Ascending { get; set; }
public string PropName {get; set;}
}
public static class SortFuncCompiler
{
private static readonly MethodInfo _strCompareTo = typeof(string).GetMethod("CompareTo", new[] {typeof(string)});
private static readonly MethodInfo _intCompareTo = typeof(int).GetMethod("CompareTo", new[] {typeof(int)});
public static Func<T,T,int> MakeSortFunc<T>(IList<SortBy> sortDescriptors)
{
ParameterExpression param1Expr = Expression.Parameter(typeof(T));
ParameterExpression param2Expr = Expression.Parameter(typeof(T));
BlockExpression exprSd = MakeCompositeCompare(param1Expr, param2Expr, sortDescriptors);
Expression<Func<T,T,int>> lambda = Expression.Lambda<Func<T,T,int>>(exprSd, param1Expr, param2Expr);
return lambda.Compile();
}
private static BlockExpression MakePropertyCompareBlock(
SortBy sortDescriptor,
ParameterExpression rm1,
ParameterExpression rm2,
LabelTarget labelReturn,
ParameterExpression result)
{
try
{
MemberExpression propA = Expression.Property(rm1, sortDescriptor.PropName);
MemberExpression propB = Expression.Property(rm2, sortDescriptor.PropName);
var (prop1, prop2) = sortDescriptor.Ascending ? (propA, propB) : (propB, propA);
Expression compareExpr;
if(prop1.Type == typeof(string))
{
compareExpr = Expression.Call(prop1, _strCompareTo, prop2);
}
else if(prop1.Type == typeof(int))
{
compareExpr = Expression.Call(prop1, _intCompareTo, prop2);
}
else
{
throw new ApplicationException($"unsupported property type: {prop1.Type}");
}
IEnumerable<ParameterExpression> variables = new[] {result};
IEnumerable<Expression> expressions = new Expression[]
{
Expression.Assign(result, compareExpr),
Expression.IfThen(
Expression.NotEqual(Expression.Constant(0), result),
Expression.Goto(labelReturn, result))
};
return Expression.Block(variables, expressions);
}
catch
{
throw new ApplicationException($"unknown property: {sortDescriptor.PropName}");
}
}
private static BlockExpression MakeCompositeCompare(ParameterExpression param1Expr, ParameterExpression param2Expr, IEnumerable<SortBy> sortBys )
{
ParameterExpression result = Expression.Variable(typeof(int), "result");
LabelTarget labelReturn = Expression.Label(typeof(int));
LabelExpression labelExpression = Expression.Label(labelReturn, result);
IEnumerable<Expression> compareBlocks = sortBys.Select(propName => MakePropertyCompareBlock(propName, param1Expr, param2Expr, labelReturn, result));
return Expression.Block(new[] {result}, compareBlocks.Append(labelExpression));
}
}
how to use the generated sort function
public class MyComparer<T> : IComparer<T>
{
private Func<T, T, int> _sortFunc;
public MyComparer(Func<T, T, int> sortFunc)
{
_sortFunc = sortFunc;
}
public int Compare(T x, T y) => _sortFunc(x, y);
}
//the expression-tree generated sorting function should be of form
static int SortOneIntOneStrHC(MyClass aa, MyClass bb)
{
int s1 = aa.IntProp1.CompareTo(bb.IntProp1);
if (s1 != 0) return s1;
// aa and bb flipped, as this comparison is descending
return bb.StrProp1.CompareTo(aa.StrProp1);
}
public class MyClass
{
public int IntProp1 { get; set; }
public int IntProp2 { get; set; }
public string StrProp1 { get; set; }
public string StrProp2 { get; set; }
}
void Main()
{
var xs = new List<MyClass>
{
new MyClass{IntProp1 = 99, IntProp2 = 88, StrProp1 = "aa", StrProp2 ="bb"},
new MyClass{IntProp1 = 11, IntProp2 = 22, StrProp1 = "xx", StrProp2 ="yy"},
new MyClass{IntProp1 = 11, IntProp2 = 22, StrProp1 = "pp", StrProp2 ="qq"},
};
var sortBys = new List<SortBy>
{
new SortBy{PropName = "IntProp2", Ascending = true},
new SortBy{PropName = "StrProp1", Ascending = false}
};
Func<MyClass, MyClass, int> sortMyClass = SortFuncCompiler.MakeSortFunc<MyClass>(sortBys);
var ys = xs.OrderBy(x => x, new MyComparer<MyClass>(sortMyClass));
ys.Dump();
}