Skip to main content
 首页 » 编程设计

c#之从 IL 构造方法引用集合

2024年02月05日29zengkefu

我正在使用反射构建一个动态方法。大多数教程和文档(例如 How to: Define and Execute Dynamic Methods 或 Creating method dynamically, and executing it)显示了一个非常简单的示例。

我试图找到一种从动态程序集中引用另一个程序集的方法。

例如,我希望能够使用Reflection.Emit 构造以下函数。

public static void f(int n) 
{ 
    int[] arr = new arr[n]; 
    return arr.Max(); 
} 

执行此操作的常用方法是什么?

请您参考如下方法:

获得有效 IL 指令的最可靠方法是用高级语言创建代码,对其进行编译和反编译。接下来,您可以使用 Reflection.Emit 重新创建指令。

我更改了您的示例函数,否则它的可测试性不好,因为结果总是一样的:

public static int f(int n) 
{ 
    int[] arr = Enumerable.Range(0, n).ToArray(); 
    return arr.Max(); 
} 

作为调试构建,ILDasm 给了我们(发布将导致更少的指令,但那时没有太多可看的):

.method public hidebysig static int32  f(int32 n) cil managed 
{ 
  // Code size       25 (0x19) 
  .maxstack  2 
  .locals init ([0] int32[] arr, 
           [1] int32 V_1) 
  IL_0000:  nop 
  IL_0001:  ldc.i4.0 
  IL_0002:  ldarg.0 
  IL_0003:  call       class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32, 
                                                                                                                                  int32) 
  IL_0008:  call       !!0[] [System.Core]System.Linq.Enumerable::ToArray<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>) 
  IL_000d:  stloc.0 
  IL_000e:  ldloc.0 
  IL_000f:  call       int32 [System.Core]System.Linq.Enumerable::Max(class [mscorlib]System.Collections.Generic.IEnumerable`1<int32>) 
  IL_0014:  stloc.1 
  IL_0015:  br.s       IL_0017 
  IL_0017:  ldloc.1 
  IL_0018:  ret 
} // end of method Program::f 

现在您可以使用 MethodBuilder/DynamicMethod 和 ILGenerator 重新创建此方法的各个方面。

public static int fNew(int n) 
{ 
    var da = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("tempAsm"), 
        AssemblyBuilderAccess.RunAndSave); 
    var dm = da.DefineDynamicModule("tempAsm", "tempAsm.dll"); 
    var dt = dm.DefineType("dynType"); 
 
    var d = dt.DefineMethod("f", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, 
        CallingConventions.Standard, typeof (int), new[] {typeof (int)}); 
    var il = d.GetILGenerator(); 
    il.DeclareLocal(typeof (int[])); 
    il.DeclareLocal(typeof (int)); 
 
    var range = typeof (Enumerable).GetMethod(nameof(Enumerable.Range)); 
    var toArray = typeof (Enumerable).GetMethod(nameof(Enumerable.ToArray)).MakeGenericMethod(typeof (int)); 
    var max = typeof (Enumerable).GetMethod(nameof(Enumerable.Max), 
        new[] {typeof (IEnumerable<>).MakeGenericType(typeof (int))}); 
 
    if (range == null || toArray == null || max == null) throw new Exception(); 
 
    var branchTarget = il.DefineLabel(); 
 
    il.Emit(OpCodes.Nop); 
    il.Emit(OpCodes.Ldc_I4_0); 
    il.Emit(OpCodes.Ldarg_0); 
    il.Emit(OpCodes.Call, range); 
    il.Emit(OpCodes.Call, toArray); 
    il.Emit(OpCodes.Stloc_0); 
    il.Emit(OpCodes.Ldloc_0); 
    il.Emit(OpCodes.Call, max); 
    il.Emit(OpCodes.Stloc_1); 
    il.Emit(OpCodes.Br_S, branchTarget); 
    il.MarkLabel(branchTarget); 
    il.Emit(OpCodes.Ldloc_1); 
    il.Emit(OpCodes.Ret); 
 
    var bakedType = dt.CreateType(); 
 
    da.Save("tempAsm.dll"); 
    var x = bakedType.GetMethod("f"); 
 
    return (int) x.Invoke(null, new object[] {n}); 
} 

测试它:

static void Main(string[] args) 
{ 
    Console.WriteLine(f(10)); 
    Console.WriteLine(fNew(10)); 
} 

它应该可以工作:

9 
9