浅谈 C# Assembly 与 IL (一):C# Assembly 与 Reflection (2)

输出如下

Dynamic Max Time Id: 0 Max Time: 2870643 Total Time: 2926522 Method.Invoke Max Time Id: 7232 Max Time: 342 Total Time: 3009383 Direct Call Max Time Id: 1342 Max Time: 23 Total Time: 3040620

由此可见,动态方法在第一次调用时效率极低,但之后的重复调用对比另外两种方案却没有太明显的差别,甚至有着一定的优势。

使用反射来向Assembly中获取或注入IL代码

Reflection本身也有提供查看和注入IL代码的方法,但其在这两方面都有很大的限制。

查看IL代码

Reflection 可以通过

MethodInfo.GetMethodBody().GetILAsByteArray();

来获取方法中的IL代码。但正如你所见,IL是以Byte数组的形式被读出来,你往往需要自己对读出的ByteArray进行额外的包装和翻译处理才能做有意义的应用,而这需要对 CIL 这门中间语言有相当程度的理解才能做到。

示范

下面我会举一个例子示范。
首先假设我们在Program类中,除了main以外还有一个方法

// 这是一个用于计算1 + 2 + ... + x 的方法,作为示范 public int SumOf1ToNum(int num) { if(num < 1) { Console.WriteLine("Sum = 0;"); return 0; } int sum = 0; for(int i = 1; i < num; i++) { sum += i; Console.Write(i + " + "); } sum += num; Console.WriteLine(num + " = " + sum); return sum; }

然后在main函数中,利用Reflection.Asssembly直接读取当前Program类所在的程序集(该示范是直接获取当前程序的程序集作为演示,大多数情况我们会用上面提到的方法获取其他程序集,这里就偷个懒了)

// 下面这两步其实是画蛇添足的,可以直接通过 typeof(Program).GetMethod() 来获取对应的MethodInfo, // 而这里是在演示从 Assembly 中获取方法,为了偷懒没有创建额外的程序集,也顺便演示一下可以这么做 Assembly assembly = typeof(Program).Assembly; // 这里注意类名需要输入全名,也就是要包含 namespace MethodInfo sumOf1ToNumMethod = assembly.GetType("AssemblyExample.Program").GetMethod("SumOf1ToNum"); byte[] ilByteArr = sumOf1ToNumMethod.GetMethodBody().GetILAsByteArray(); // BitConverter 也是C#中非常实用的工具,常用于各个基本类型与二进制数据间的转换 Console.WriteLine(BitConverter.ToString(ilByteArr));

用DnSpy或ILSpy等工具查看,你将看到

上面这段代码的Output为:
00-03-17-FE-04-0B-07-2C-10-00-72-49-00-00-70-28-16-00-00-0A-00-16-0C-2B-54-16-0A-17-0D-2B-20-00-06-09-58-0A-09-8C-19-00-00-01-72-5B-00-00-70-28-18-00-00-0A-28-19-00-00-0A-00-00-09-17-58-0D-09-03-FE-04-13-04-11-04-2D-D6-06-03-58-0A-03-8C-19-00-00-01-72-63-00-00-70-06-8C-19-00-00-01-28-1A-00-00-0A-28-16-00-00-0A-00-06-0C-2B-00-08-2A

嗯。。。你或许已经发现,这串代码根本不是给人读的,但 Reflection 似乎只打算给你看这个(至少我只找到这个,欢迎指正),Thanks, Microsoft!

动态生成 IL 代码

Reflection 在 Reflection.Emit 的命名空间下提供了各种 Builder 类用于动态的输出 IL 代码,这些只能动态的生成新的 Runtime Type 以及新的 Assembly,而并不能直接修改已经存在的 Assembly,如此一来能应用到的方面就非常局限了(不过好歹比 GetILAsByteArray() 实用一点)。

利用反射动态生成代码最常用的两种方式为下:

生成动态方法,并利用托管或接口调用

DynamicMethod dynamicSum = new DynamicMethod("GetSum",typeof(int), new Type[] {typeof(int), typeof(int)}, typeof(Program).Module); ILGenerator generator = dynamicSum.GetILGenerator(); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldarg_1); generator.Emit(OpCodes.Add); generator.Emit(OpCodes.Ret); var getSum = (GetSumDelegate)dynamicSum.CreateDelegate(typeof(GetSumDelegate)); Console.WriteLine(getSum(1, 3));

生成 Runtime 程序集,然后动态调用方法

AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("RuntimeAssembly"), AssemblyBuilderAccess.Run); ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule"); TypeBuilder typeBuilder = moduleBuilder.DefineType("MyRuntimeType", TypeAttributes.Public); MethodBuilder methodBuilder = typeBuilder.DefineMethod("RuntimeSum", MethodAttributes.Public | MethodAttributes.Static); methodBuilder.SetParameters(new Type[] {typeof(int), typeof(int)}); methodBuilder.SetReturnType(typeof(int)); ILGenerator generator = methodBuilder.GetILGenerator(); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldarg_1); generator.Emit(OpCodes.Add); generator.Emit(OpCodes.Ret); Type dynamicType = typeBuilder.CreateType(); var dynamicMethod = dynamicType.GetMethod("RuntimeSum", BindingFlags.Public | BindingFlags.Static); assemblyBuilder.SetEntryPoint(dynamicMethod); Console.WriteLine("Sum of 1 + 2 = " + assemblyBuilder.EntryPoint.Invoke(null, new object[] { 1, 2 })); Console.WriteLine("Sum of 1 + 2 = " + dynamicMethod.Invoke(null, new object[] { 1, 2}));

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpwwpp.html