为了账号安全,请及时绑定邮箱和手机立即绑定

通过 ILGenerator 调用带有谓词表达式的 LINQ

通过 ILGenerator 调用带有谓词表达式的 LINQ

C#
吃鸡游戏 2022-07-23 17:17:54
我正在尝试通过在运行时发出 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。如何将表达式传递给编译方法或FieldInfo正确调用OrderByDescending()?
查看完整描述

1 回答

?
隔江千里

TA贡献1906条经验 获得超10个赞

我不完全理解你想通过直接 IL 生成来实现什么;OrderByDescending接受一个Func<TSource, TKey>名为“keySelector”的参数。因此,您在仍然使用此方法时唯一可以生成的 IL 只是一个常规方法调用,它将“keySelector”参数传递给该方法,除非您打算在 ILOrderByDescending中重新实现。OrderByDescending


你有什么理由需要一直下到 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>));

}



查看完整回答
反对 回复 2022-07-23
  • 1 回答
  • 0 关注
  • 110 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信