通过ILGenerator用谓词表达式调用LINQ

时间:2019-01-16 04:11:17

标签: c# linq cil

我试图通过在运行时发出IL来编译DynamicMethod。我希望它执行以下操作:

array.OrderByDesc( /* Select Field/Property Expression*/ ).ToArray();

编译DynamicMethod的方法具有一个FieldInfo变量,我想将其用于OrderByDesc所需的表达式。

这是我到目前为止所拥有的:

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
}

注意事项:

  • 提供的表达式是一个选择器,用于指定在整个已编译方法中使用哪个字段(或属性支持值)。
  • 此方法的功能远不止调用OrderByDescending(),而且还包含许多底层优化。除排序外,大多数情况下预计运行时间都在40ns以下。

如何使用传递到compile方法中的表达式或FieldInfo来正确调用OrderByDescending()

1 个答案:

答案 0 :(得分:3)

我不完全了解您要通过直接IL生成实现的目标; OrderByDescending采用名为“ keySelector”的Func<TSource, TKey>参数。因此,即使您仍打算在IL中重新实现OrderByDescending,您仍然可以使用此方法生成的唯一IL只是一个常规方法调用,该方法将“ keySelector”参数传递给OrderByDescending方法。 / p>

您是否有理由一路下降到IL?

如果这是用于用户级别的代码,则可以“编译”将传递给该方法的expression并正常调用OrderByDescending(),例如

var expression = /* Select Field/Property Expression*/;
array.OrderByDescending(expression.Compile()).ToArray();

如果这是框架/实用程序级别的代码,则可能无需使用“表达式树”就可以完全摆脱手动IL的困扰。例如

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();
}

但是,如果由于某些原因您仍然需要直接通过IL发出此信号,则可以执行以下操作。

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>));
}