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

我正在尝试通过在运行时发出 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()?


吃鸡游戏
浏览 172回答 1
1回答

隔江千里

我不完全理解你想通过直接 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){&nbsp; &nbsp; var parameter = Expression.Parameter(typeof(IEnumerable<T>), "source");&nbsp; &nbsp; // Your `GetMethod` for OrderByDescending did not work for me,&nbsp; &nbsp; // so I'll just hand wave about this.&nbsp; &nbsp; var orderByDescMethod = typeof(Enumerable)&nbsp; &nbsp; &nbsp; &nbsp; .GetMethods()&nbsp; &nbsp; &nbsp; &nbsp; .Single(m => m.Name == nameof(Enumerable.OrderByDescending) &&&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;m.GetParameters().Length == 2)&nbsp; &nbsp; &nbsp; &nbsp; .MakeGenericMethod(typeof(T), typeof(double));&nbsp; &nbsp; var toArrayMethod = typeof(Enumerable)&nbsp; &nbsp; &nbsp; &nbsp; .GetMethod(nameof(Enumerable.ToArray))&nbsp; &nbsp; &nbsp; &nbsp; .MakeGenericMethod(typeof(T));&nbsp; &nbsp; var orderByExpression = Expression.Call(orderByDescMethod, parameter, expression);&nbsp; &nbsp; var lambdaBody = Expression.Call(toArrayMethod, orderByExpression);&nbsp; &nbsp; var lambdaExpression = Expression.Lambda<FilterDelegate<T>>(lambdaBody, parameter);&nbsp; &nbsp; return lambdaExpression.Compile();}但是,如果由于某种原因您仍然需要直接通过 IL 发出它,那么类似下面的东西可以工作。public static FilterDelegate<T> CreateDelegate<T>(Expression<Func<T, double>> expression){&nbsp; &nbsp; // Your `GetMethod` for OrderByDescending did not work for me,&nbsp; &nbsp; // so I'll just hand wave about this.&nbsp; &nbsp; var orderByDescMethod = typeof(Enumerable)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .GetMethods()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .Single(m => m.Name == nameof(Enumerable.OrderByDescending) &&&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;m.GetParameters().Length == 2)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .MakeGenericMethod(typeof(T), typeof(double));&nbsp; &nbsp; var toArrayMethod = typeof(Enumerable)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .GetMethod(nameof(Enumerable.ToArray))&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .MakeGenericMethod(typeof(T));&nbsp; &nbsp; // TODO: if you don't already have one of these&nbsp; &nbsp; //&nbsp; &nbsp; &nbsp; &nbsp;you'll probably want to pull this out and re-use it&nbsp; &nbsp; //&nbsp; &nbsp; &nbsp; &nbsp;rather than making a new one for every delegate&nbsp; &nbsp; // TODO: if you do share a module builder I don't think it's thread-safe&nbsp; &nbsp; //&nbsp; &nbsp; &nbsp; &nbsp;so this method will need sufficient locking/synchronization&nbsp; &nbsp; var dynamicAssemblyName = new AssemblyName { Name = $"{Guid.NewGuid()}" };&nbsp; &nbsp; var asm = AppDomain.CurrentDomain.DefineDynamicAssembly(dynamicAssemblyName, AssemblyBuilderAccess.Run);&nbsp; &nbsp; var module = asm.DefineDynamicModule(dynamicAssemblyName.Name);&nbsp; &nbsp; // Create a class with a static field to hold our compiled expression&nbsp; &nbsp; var typeBuilder = module.DefineType(&nbsp; &nbsp; &nbsp; &nbsp; $"{Guid.NewGuid()}",&nbsp; &nbsp; &nbsp; &nbsp; TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Sealed | TypeAttributes.Serializable);&nbsp; &nbsp; var compiledExpressionField = typeBuilder.DefineField(&nbsp; &nbsp; &nbsp; &nbsp; "CompiledExpression",&nbsp; &nbsp; &nbsp; &nbsp; typeof(Func<T, double>),&nbsp; &nbsp; &nbsp; &nbsp; FieldAttributes.Static | FieldAttributes.Private);&nbsp; &nbsp; var holderType = typeBuilder.CreateType();&nbsp; &nbsp; var compiledExpression = expression.Compile();&nbsp; &nbsp; // Get the actual field after we've compiled the type&nbsp; &nbsp; var compiledExpressionFieldInfo = holderType.GetField(&nbsp; &nbsp; &nbsp; &nbsp; compiledExpressionField.Name,&nbsp; &nbsp; &nbsp; &nbsp; BindingFlags.Static | BindingFlags.NonPublic);&nbsp; &nbsp; // Store the compiled expression in the static field&nbsp; &nbsp; compiledExpressionFieldInfo.SetValue(null, compiledExpression);&nbsp; &nbsp; var newDelegate = new DynamicMethod($"{Guid.NewGuid()}",&nbsp; &nbsp; &nbsp; &nbsp; typeof(IOrderedEnumerable<T>),&nbsp; &nbsp; &nbsp; &nbsp; new[] { typeof(IEnumerable<T>) },&nbsp; &nbsp; &nbsp; &nbsp; typeof(ILGen), true);&nbsp; &nbsp; var il = newDelegate.GetILGenerator();&nbsp; &nbsp; // Load the array passed into the Delegate (T[])&nbsp; &nbsp; il.Emit(OpCodes.Ldarg_0);&nbsp; &nbsp; // Load the compiled expression from a static field&nbsp; &nbsp; il.Emit(OpCodes.Ldsfld, compiledExpressionFieldInfo);&nbsp; &nbsp; // Call .OrderByDescending()&nbsp; &nbsp; il.Emit(OpCodes.Call, orderByDescMethod);&nbsp; &nbsp; // Call .ToArray()&nbsp; &nbsp; il.Emit(OpCodes.Call, toArrayMethod);&nbsp; &nbsp; il.Emit(OpCodes.Ret); // Stores the sorted array&nbsp; &nbsp; return (FilterDelegate<T>)newDelegate.CreateDelegate(typeof(FilterDelegate<T>));}
打开App,查看更多内容
随时随地看视频慕课网APP