I am trying to compile a DynamicMethod by emitting IL at runtime. I would like it to perform the following:
array.OrderByDesc( /* Select Field/Property Expression*/ ).ToArray();
The method that compiles the DynamicMethod has a FieldInfo
variable that I would like to use for the expression that OrderByDesc
requires.
Here is what I have so far:
public static FilterDelegate<T> CreateDelegate<T>( Expression<Func<T, double>> expression )
{
var field = expression.GetFieldInfo();// Extension, gets FieldInfo from expression
...
il.Emit( OpCodes.Ldloc_1 ); // Loads an array (T[])
il.Emit( OpCodes.Call, typeof( Enumerable ).GetMethod( nameof( Enumerable.OrderByDescending ), new Type[0]).MakeGenericMethod( typeof( T ) ) );
il.Emit( OpCodes.Call, typeof( Enumerable ).GetMethod( nameof( Enumerable.ToArray ) ).MakeGenericMethod( typeof( T ) ) );
il.Emit( OpCodes.Stloc_1 ); // Stores the sorted array
}
A few things to note:
OrderByDescending()
and contains a lot of low level optimization. Excluding the sort, it is expected to run in under 40ns in most cases.How can I take either the expression passed into the compile method or the FieldInfo
to properly call OrderByDescending()
?
I don't fully understand what you're trying to achieve with direct IL Generation; OrderByDescending
takes a Func<TSource, TKey>
parameter named "keySelector". So the only IL you can generate while still using this method would just be a regular method call that passes a "keySelector" argument to the OrderByDescending
method unless you intend on re-implementing OrderByDescending
in IL.
Is there a reason you need to drop down all the way to IL?
If this is for user level code, you can "Compile" the expression
that would've been passed to this method and call OrderByDescending()
normally e.g.
var expression = /* Select Field/Property Expression*/;
array.OrderByDescending(expression.Compile()).ToArray();
If this is framework/utility level code, you could potentially get away with using "Expression trees" without going all the way down to manual IL. e.g.
public static FilterDelegate<T> CreateDelegate<T>(Expression<Func<T, double>> expression)
{
var parameter = Expression.Parameter(typeof(IEnumerable<T>), "source");
// Your `GetMethod` for OrderByDescending did not work for me,
// so I'll just hand wave about this.
var orderByDescMethod = typeof(Enumerable)
.GetMethods()
.Single(m => m.Name == nameof(Enumerable.OrderByDescending) &&
m.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), typeof(double));
var toArrayMethod = typeof(Enumerable)
.GetMethod(nameof(Enumerable.ToArray))
.MakeGenericMethod(typeof(T));
var orderByExpression = Expression.Call(orderByDescMethod, parameter, expression);
var lambdaBody = Expression.Call(toArrayMethod, orderByExpression);
var lambdaExpression = Expression.Lambda<FilterDelegate<T>>(lambdaBody, parameter);
return lambdaExpression.Compile();
}
If however, you still need to emit this directly via IL for some reason then something like the following can work.
public static FilterDelegate<T> CreateDelegate<T>(Expression<Func<T, double>> expression)
{
// Your `GetMethod` for OrderByDescending did not work for me,
// so I'll just hand wave about this.
var orderByDescMethod = typeof(Enumerable)
.GetMethods()
.Single(m => m.Name == nameof(Enumerable.OrderByDescending) &&
m.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), typeof(double));
var toArrayMethod = typeof(Enumerable)
.GetMethod(nameof(Enumerable.ToArray))
.MakeGenericMethod(typeof(T));
// TODO: if you don't already have one of these
// you'll probably want to pull this out and re-use it
// rather than making a new one for every delegate
// TODO: if you do share a module builder I don't think it's thread-safe
// so this method will need sufficient locking/synchronization
var dynamicAssemblyName = new AssemblyName { Name = $"{Guid.NewGuid()}" };
var asm = AppDomain.CurrentDomain.DefineDynamicAssembly(dynamicAssemblyName, AssemblyBuilderAccess.Run);
var module = asm.DefineDynamicModule(dynamicAssemblyName.Name);
// Create a class with a static field to hold our compiled expression
var typeBuilder = module.DefineType(
$"{Guid.NewGuid()}",
TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Sealed | TypeAttributes.Serializable);
var compiledExpressionField = typeBuilder.DefineField(
"CompiledExpression",
typeof(Func<T, double>),
FieldAttributes.Static | FieldAttributes.Private);
var holderType = typeBuilder.CreateType();
var compiledExpression = expression.Compile();
// Get the actual field after we've compiled the type
var compiledExpressionFieldInfo = holderType.GetField(
compiledExpressionField.Name,
BindingFlags.Static | BindingFlags.NonPublic);
// Store the compiled expression in the static field
compiledExpressionFieldInfo.SetValue(null, compiledExpression);
var newDelegate = new DynamicMethod($"{Guid.NewGuid()}",
typeof(IOrderedEnumerable<T>),
new[] { typeof(IEnumerable<T>) },
typeof(ILGen), true);
var il = newDelegate.GetILGenerator();
// Load the array passed into the Delegate (T[])
il.Emit(OpCodes.Ldarg_0);
// Load the compiled expression from a static field
il.Emit(OpCodes.Ldsfld, compiledExpressionFieldInfo);
// Call .OrderByDescending()
il.Emit(OpCodes.Call, orderByDescMethod);
// Call .ToArray()
il.Emit(OpCodes.Call, toArrayMethod);
il.Emit(OpCodes.Ret); // Stores the sorted array
return (FilterDelegate<T>)newDelegate.CreateDelegate(typeof(FilterDelegate<T>));
}