前言

动态类型一般在编写框架的时候使用的比较多,如动态生成数据实体模型,已知接口类型和注解[Attribute],动态实现http请求接口等,使用动态类型可以节约大量重复代码。目前动态代理类型做的比较优秀的类库Castle.Core,他可以做到动态代理(DynamicProxy),审核日志(Logging)、适配器(DictionaryAdapter)等,其本质就是生成一个代理类型,下面我们就自己实现一个这样的类型(示例代码仅供参考)

因为不熟悉IL所以我们先把要实现的类型与接口写出来,有人会说都写出来了还要它干嘛?其实在现实框架设计中既然选择了动态代理那么就说明不是一个类型,而是多个同一类型却又不相同的各代理类型,我们写出来是把他作为模板,方便使用Emit IL 动态创建,下面就是要实现的类型:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EmitTest
{
    public interface ILogService
    {
        void WriteLog(string log);
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EmitTest
{
    public class LogService : ILogService
    {
        private readonly int _start;
        private readonly string _name;
        public LogService(int start, string name
            )
        {
            _start = start;
            _name = name;
        }
        public void WriteLog(string log)
        {
            Console.WriteLine(_start);
            Console.WriteLine(_name);
            Console.WriteLine(log);
        }
    }
}

创建完成后编译生成dll,在通过反编译工具来看,我使用的是ILSpy,如下图:

构造函数部分:截图与代码注释对应如ldarg.0为this入栈

asp.net core IL动态构创建类型_Emit

普通方法:

asp.net core IL动态构创建类型_System_02

我再编写动态实现的时候如下代码:

using EmitTest;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var type= CreateType();
            var obj =(ILogService) Activator.CreateInstance(type,new object[] { 10,"hecong"});
            obj.WriteLog("wwww");
            Console.ReadKey();
        }

        public static Type CreateType()
        {

            //先定义一个AssemblyBulider
            AssemblyBuilder assemblyBuilder =
                AssemblyBuilder.DefineDynamicAssembly(
                    new AssemblyName("EmitTest"), AssemblyBuilderAccess.RunAndSave);
            //moduleBuilder
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("EmitTestModule.dll");
            //TypeBuilder 
            TypeBuilder typeBuilder = moduleBuilder.DefineType("LogService", TypeAttributes.Class | TypeAttributes.Public);
            typeBuilder.AddInterfaceImplementation(typeof(ILogService));
            //构造字段
            FieldBuilder fieldstart = typeBuilder.DefineField("_start", typeof(int), FieldAttributes.Private);
            FieldBuilder fieldName = typeBuilder.DefineField("_name", typeof(string), FieldAttributes.Private);
            //然后是构造方法
            //ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.ExplicitThis,
            //    new Type[] { typeof(int), typeof(string) });
            ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard,
                new Type[] { typeof(int),typeof(string) });
            ////构造方法体
            ILGenerator iL = constructorBuilder.GetILGenerator();
            iL.Emit(OpCodes.Ldarg_0);//相当于this入栈
            //                         //调用object类构造方法,因为当前是一个类,相当于调用父类构造函数,当前父类是object,如有继承为继承类
            iL.Emit(OpCodes.Call, typeof(object).GetConstructor(new Type[0]));//我们要调用不带参数的,
            //_start=start;参数赋值给字段
            //iL.Emit(OpCodes.Nop);
            //iL.Emit(OpCodes.Nop);
            //this 入栈
            iL.Emit(OpCodes.Ldarg_0);
            //第一个字段
            iL.Emit(OpCodes.Ldarg_1);
            ////把栈内数据赋值给字段,这种是需要对象this的,所以先入栈this
            iL.Emit(OpCodes.Stfld, fieldstart);

            //_name=name,同上
            iL.Emit(OpCodes.Ldarg_0);
            iL.Emit(OpCodes.Ldarg_2);
            iL.Emit(OpCodes.Stfld, fieldName);
            //结束构造方法
            iL.Emit(OpCodes.Ret);
            //构建普通方法


            MethodBuilder mbIM = typeBuilder.DefineMethod("WriteLog",
           MethodAttributes.Public | MethodAttributes.HideBySig |
               MethodAttributes.NewSlot | MethodAttributes.Virtual |
               MethodAttributes.Final,
           null,
           new Type[] { typeof(string) });

            var ilMethod = mbIM.GetILGenerator();
            //ilMethod.Emit(OpCodes.Nop);
            //this 入栈
            ilMethod.Emit(OpCodes.Ldarg_0);
            //读取_start字段
            ilMethod.Emit(OpCodes.Ldfld, fieldstart);
            ////ilMethod.Emit(OpCodes.Call,);
            //使用_start字段
            ilMethod.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine",
                new Type[] { typeof(int) }));
            //ilMethod.Emit(OpCodes.Nop);
            //var local = ilMethod.DeclareLocal(typeof(string)); // create a local variable
            //读取_name字段
            ilMethod.Emit(OpCodes.Ldarg_0);
            ilMethod.Emit(OpCodes.Ldfld, fieldName);
            //使用_name字段
            ilMethod.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine",
                new Type[] { typeof(string) }));
            //ilMethod.Emit(OpCodes.Nop);
            ilMethod.Emit(OpCodes.Ldarg_1);
            ilMethod.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine",
                new Type[] { typeof(object) }));
           // ilMethod.Emit(OpCodes.Nop);
            ilMethod.Emit(OpCodes.Ret);
            typeBuilder.DefineMethodOverride(mbIM, typeof(ILogService).GetMethod("WriteLog"));
            Type personType = typeBuilder.CreateType();
            assemblyBuilder.Save("LogService.dll");
            return personType;
        }
    }
}

上述方法中字段请对照看,示例项目为复制修改产物,变量命名不代表任何含义,但方法名,要与接口中的完全一样。

通过上述代码与贴图,emit代码怎么写是不是也有了参考,通过测试,与所想符合。