Emit技术使用实例及应用思路

System.Reflection.Emit提供了动态创建类并生成程序集的功能。 适用于.NET Framework 2.0及其以后的版本。

动态生成类在对于O/R Mapping来说有很大的作用,在实际应用中用到的一些场景,比如支持用户自定义字段,自定义公式的解析、动态插件等等。

下面就详细介绍一下Emit的实际应用:

 

Emit动态创建类型

目标:创建类型Employee,两个简单属性:Name(姓名),BirthDate(生日) ,一个方法void PropertyChanged(string propertyName)

其中Name必填,预设值“新同事”,String类型;BirthDate是日期类型

 

准备工作:定义类型EntityDictionary和DynamicField,用于动态产生实体类型。

 

public class EntityDictionary {
        private string _name;
        /// <summary>
        /// 实体名称
        /// </summary>
        public string Name {
            get { return _name; }
            set { _name = value; }
        }

        private string _caption;
        /// <summary>
        /// 显示值
        /// </summary>
        public string Caption {
            get { return _caption; }
            set { _caption = value; }
        }

        private List<string> _interfaceList;
        /// <summary>
        /// 接口列表
        /// </summary>
        public List<string> InterfaceList {
            get { return _interfaceList; }
            set { _interfaceList = value; }
        }

        private List<DynamicField> _dynamicFieldList;
        /// <summary>
        /// 字段列表
        /// </summary>
        public List<DynamicField> DynamicFieldList {
            get { return _dynamicFieldList; }
            set { _dynamicFieldList = value; }
        }
    }

 

 

public class DynamicField {
        private string _name;
        /// <summary>
        /// 字段名称
        /// </summary>
        public string Name {
            get { return _name; }
            set { _name = value; }
        }

        private string _entityName;
        /// <summary>
        /// 实体名称
        /// </summary>
        public string EntityName {
            get { return _entityName; }
            set { _entityName = value; }
        }

        private string _type;
        /// <summary>
        /// 类型
        /// </summary>
        public string Type {
            get { return _type; }
            set { _type = value; }
        }

        private int _size;
        /// <summary>
        /// 长度
        /// </summary>
        public int Size {
            get { return _size; }
            set { _size = value; }
        }

        private byte _scale;
        /// <summary>
        /// 小数位数
        /// </summary>
        public byte Scale {
            get { return _scale; }
            set { _scale = value; }
        }


        private string _refType;
        /// <summary>
        /// 引用类型
        /// </summary>
        public string RefType {
            get { return _refType; }
            set { _refType = value; }
        }

        private string _control;
        /// <summary>
        /// 显示控件类型
        /// </summary>
        public string Control {
            get { return _control; }
            set { _control = value; }
        }

        private string _caption;
        /// <summary>
        /// 显示值
        /// </summary>
        public string Caption {
            get { return _caption; }
            set { _caption = value; }
        }

        private bool _isRequired;
        /// <summary>
        /// 是否必填
        /// </summary>
        public bool IsRequired {
            get { return _isRequired; }
            set { _isRequired = value; }
        }

        private string _defaultValue;
        /// <summary>
        /// 默认值
        /// </summary>
        public string DefaultValue {
            get { return _defaultValue; }
            set { _defaultValue = value; }
        }

        private string _externInfo;
        /// <summary>
        /// 扩展信息
        /// </summary>
        public string ExternInfo {
            get { return _externInfo; }
            set { _externInfo = value; }
        }

        private object _tempProperty;
        /// <summary>
        /// 保留栏位
        /// </summary>
        public object TempProperty {
            get { return _tempProperty; }
            set { _tempProperty = value; }
        }
    }

 

1.创建类

 

首先我们看下产生dll的代码:

public void Create() {
            AppDomain myDomain = Thread.GetDomain();
            AssemblyName myAsmName = new AssemblyName();
            myAsmName.Name = "TestDynamicAssembly";
            myAsmName.Version = new Version("1.0.0.0");            
         //创建一个永久程序集,设置为AssemblyBuilderAccess.RunAndSave。
              AssemblyBuilder myAsmBuilder = myDomain.DefineDynamicAssembly(myAsmName,
                                                            AssemblyBuilderAccess.RunAndSave);
            //设置版本号信息
            myAsmBuilder.DefineVersionInfoResource("Test", myAsmName.Version.ToString(), "TestCorp", "Copyright © TestCorp Limited 2014", "");
             //创建一个永久单模程序块。   
            ModuleBuilder myModBuilder =
                myAsmBuilder.DefineDynamicModule(myAsmName.Name, myAsmName.Name + ".dll");

            //通常采用Xml读取方式给DynamicField的属性赋值,这里Demo采用简单的直接赋值方式
            List<DynamicField> listDynamicField = new List<DynamicField>();
            DynamicField df = new DynamicField();
            df = new DynamicField();
            df.Name = "Name";
            df.Caption = "姓名";
            df.Type = "System.String";
            df.IsRequired = true;//是否必填
            df.DefaultValue = "新同事";//预设值
            df.Size = 200;//字段长度
            listDynamicField.Add(df);
            
            df.Name = "BirthDate";
            df.Caption = "生日";
            df.Type = "System.DateTime";
            listDynamicField.Add(df);

            //通常采用Xml读取方式给EntityDictionary的属性赋值,这里Demo采用简单的直接赋值方式
            EntityDictionary ed = new EntityDictionary();
            ed.Name = "Employee";
            ed.Caption = "雇员信息";
            ed.DynamicFieldList = listDynamicField;
        //创建类型
            CreateEntity(myModBuilder, ed);
       //创建方法
          AddMethordToTypeBuilder(myModBuilder);
            //保存程序集。   
            myAsmBuilder.Save(myAsmName.Name + ".dll");
        }

 

 

主要代码CreateEntity(创建类型)如下:

 

private void CreateEntity(ModuleBuilder myModBuilder, EntityDictionary ed) {
            //重写命名空间
            string typeName = string.Format("{0}.{1}", "TestDemo.DataEntity", ed.Name);
            //创建TypeBuilder。   
            TypeBuilder myTypeBuilder = myModBuilder.DefineType(typeName,
                                                            TypeAttributes.Public);

            #region 类标记
            //序列化
            CustomAttributeBuilder customAttributeBuilder = new CustomAttributeBuilder(typeof(SerializableAttribute).GetConstructor(Type.EmptyTypes), new Type[] { });
            myTypeBuilder.SetCustomAttribute(customAttributeBuilder);

            //这是个相对复杂的类型标记 定义了类型的主键和别名,有兴趣可以研究一下
            //if (!string.IsNullOrEmpty(df.Alias)) {
            //    customAttributeBuilder = new CustomAttributeBuilder(typeof(DataEntityAttribute).GetConstructor(Type.EmptyTypes), new Type[] { }, new PropertyInfo[] { typeof(DataEntityAttribute).GetProperty("PrimaryKey"), typeof(DataEntityAttribute).GetProperty("Alias") }, new object[] { df.PrimaryKey, df.Alias });
            //} else {
            //    customAttributeBuilder = new CustomAttributeBuilder(typeof(DataEntityAttribute).GetConstructor(Type.EmptyTypes), new Type[] { }, new PropertyInfo[] { typeof(DataEntityAttribute).GetProperty("PrimaryKey") }, new object[] { df.PrimaryKey });
            //}
            //myTypeBuilder.SetCustomAttribute(customAttributeBuilder);
            #endregion

            #region 接口
            //对类型添加接口
            //if (df.InterfaceList != null && df.InterfaceList.Count > 0) {
            //    AddInterface(myTypeBuilder, df.InterfaceList);
            //}
            #endregion

            #region 常量和变量
            FieldBuilder fb = myTypeBuilder.DefineField("TYPEKEY", typeof(string), FieldAttributes.Public | FieldAttributes.FamANDAssem | FieldAttributes.Family | FieldAttributes.Static | FieldAttributes.Literal | FieldAttributes.HasDefault);
            fb.SetConstant(ed.Name);
            #endregion

            //添加属性到TypeBuilder。   
            AddPropertyToTypeBuilder(myTypeBuilder, ed.DynamicFieldList);

            myTypeBuilder.CreateType();
        }

 

到这里,我们已经创建了dll的版本信息,类型的命名空间、类标记、常量等信息,此时如果排除添加属性的方法,已经可以编译成dll了,反编译的结果如下:

 

image

 

2.添加属性

属性是实体类型的重要组成部分

 

private void AddPropertyToTypeBuilder(TypeBuilder myTypeBuilder, List<DynamicField> dfList) {
            PropertyBuilder custNamePropBldr;
            MethodBuilder custNameGetPropMthdBldr;
            MethodBuilder custNameSetPropMthdBldr;
            MethodAttributes getSetAttr;
            ILGenerator custNameGetIL;
            ILGenerator custNameSetIL;

            // 属性Set和Get方法要一个专门的属性。这里设置为Public。   
            getSetAttr =
                MethodAttributes.Public | MethodAttributes.SpecialName |
                    MethodAttributes.HideBySig;

            foreach (DynamicField df in dfList) {
                try {
                    string fieldPrivateName = "_" + df.Name.Substring(0, 1).ToLower() + df.Name.Substring(1);
                    //定义字段。
                    FieldBuilder customerNameBldr = myTypeBuilder.DefineField(fieldPrivateName,Type.GetType(df.Type),
                                                                              FieldAttributes.Private);
                    //定义属性。
                    custNamePropBldr = myTypeBuilder.DefineProperty(df.Name,
                                                                     System.Reflection.PropertyAttributes.RTSpecialName,
                                                                     Type.GetType(df.Type),
                                                                     null);

                    CustomAttributeBuilder customAttributeBuilder = new CustomAttributeBuilder(typeof(DescriptionAttribute).GetConstructor(new Type[] { typeof(string) }), new object[] { df.Caption });
                    custNamePropBldr.SetCustomAttribute(customAttributeBuilder);//字段描述

                    #region 增加必填字段属性和特殊默认值属性
                    if (df.IsRequired) {
                        customAttributeBuilder = new CustomAttributeBuilder(typeof(System.ComponentModel.DataAnnotations.RequiredAttribute).GetConstructor(Type.EmptyTypes), new object[] { });
                        custNamePropBldr.SetCustomAttribute(customAttributeBuilder);//是否必填
                    }
                    if (!string.IsNullOrEmpty(df.DefaultValue)) {
                        customAttributeBuilder = new CustomAttributeBuilder(typeof(DefaultValueAttribute).GetConstructor(new Type[] { typeof(string) }), new object[] { df.DefaultValue });
                        custNamePropBldr.SetCustomAttribute(customAttributeBuilder);//特殊默认值
                    }
                    #endregion

                    //定义Get方法。   
                    custNameGetPropMthdBldr =
                        myTypeBuilder.DefineMethod("get_" + df.Name,
                                                   getSetAttr,
                                                   Type.GetType(df.Type),
                                                   Type.EmptyTypes);

                    custNameGetIL = custNameGetPropMthdBldr.GetILGenerator();
                    custNameGetIL.Emit(OpCodes.Ldarg_0);
                    custNameGetIL.Emit(OpCodes.Ldfld, customerNameBldr);
                    custNameGetIL.Emit(OpCodes.Ret);
                    
                    //把创建的两个方法(Get,Set)加入到PropertyBuilder中。   
                    custNamePropBldr.SetGetMethod(custNameGetPropMthdBldr);

                    //定义Set方法。   
                    custNameSetPropMthdBldr =
                        myTypeBuilder.DefineMethod("set_" + df.Name,
                                                   getSetAttr,
                                                   null,
                                                   new Type[] { Type.GetType(df.Type) });

                    custNameSetIL = custNameSetPropMthdBldr.GetILGenerator();

                    custNameSetIL.Emit(OpCodes.Ldarg_0);
                    custNameSetIL.Emit(OpCodes.Ldarg_1);
                    custNameSetIL.Emit(OpCodes.Stfld, customerNameBldr);
                    custNameSetIL.Emit(OpCodes.Ret);
                    //把创建的两个方法(Get,Set)加入到PropertyBuilder中。   
                    custNamePropBldr.SetSetMethod(custNameSetPropMthdBldr);
                } catch (Exception ex) {
                    throw new Exception("AddPropertyToTypeBuilder Error:" + ex.Message);
                }
            }
        }

 

再生成dll反编译,结果如下:

 

image

 

 

3.添加方法

 

在添加属性的代码中,有一部分是IL语言的反射,估计是Emit中让人觉得难以理解的代码,添加方法的时候就更需要深入理解IL语言的转换,从而实现对方法的动态添加。

例如方法void PropertyChanged(string propertyName),先另外用C#写好代码:

 

    public void PropertyChanged(string propertyName) {
            if (propertyName != null) {//判断语句
                Console.WriteLine(propertyName + " Changed!");
            }
        }

 

然后生成dll,利用反编译工具得到这段代码的IL语言

 

image

 

了解一些IL语言的基本指令,就会很容易得到这段代码:

 

private void AddMethordToTypeBuilder(TypeBuilder myTypeBuilder) {
            MethodBuilder methodBuilder = myTypeBuilder.DefineMethod("PropertyChanged", MethodAttributes.Public, null, new Type[] { typeof(string) });
            ILGenerator il = methodBuilder.GetILGenerator();
            Label lb_001 = il.DefineLabel();//lable
            il.Emit(OpCodes.Nop);
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Ldnull);
            il.Emit(OpCodes.Ceq);
            il.Emit(OpCodes.Stloc_0);
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Brtrue_S, lb_001);

            il.Emit(OpCodes.Nop);
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Ldstr, " Changed!");
            il.Emit(OpCodes.Call, typeof(string).GetMethod("Concat", new Type[] { typeof(string), typeof(string) }));

            il.Emit(OpCodes.Call, typeof(System.Console).GetMethod("WriteLine", new Type[] { typeof(string)}));
            il.Emit(OpCodes.Nop);
            il.MarkLabel(lb_001);
            il.Emit(OpCodes.Nop);
            il.Emit(OpCodes.Ret);
        }

 

再生成dll,反编译看看结果:

 

image

 

 

这样,一个简单的类就产生了,有了类标记、字段、属性、方法。

但是,现实需求不仅如此,我们还有可能需要有继承、接口等,来完善我们的类型。

 

继承和接口添加

      //创建TypeBuilder。   
            TypeBuilder myTypeBuilder = myModBuilder.DefineType(typeName,
                                                            TypeAttributes.Public);

          myTypeBuilder.SetParent(type);//继承
myTypeBuilder.AddInterfaceImplementation(typeInterface);//接口

 

需要注意的是,添加接口必须要添加接口实现的方法或者属性,否则编译会失败。

 

Emit应用场景的开发思路

 

一开始提到的Emit应用场景,实现用户级自定义字段

步骤1.新增用户自定义字段的工具,其实就是维护一份或多份Xml,记录基类实体的Key和新增的字段等信息。并提供按钮生成dll。

      上述Demo中的字段都是可以通过Xml来读取相关信息,包括类标记、属性、属性标记、基类、接口等,还可以在工具上新增字段对应的控件布局的编辑器,用于保存控件布局。

 

步骤2.在程序中通过反射引用生成的dll,并在注册实体和类型的关系字典时增加特殊判断。

     拿上面的Employee举例,首先我们的程序中需要继承EmployeeEx:Employee,在O\R Mapping中,

实体的增删改查通常是封装好的方法,例如Create("Employee") 后台会通过"Employee"找到类型Employee做实例化,

这就需要一开始对实体和实体的Key放到一个字典中,如"Employee"对应Employee,"Job"对应Job。如果我们在建立对应关系的字典时发现有Emit动态产生的dll,将相应的对应关系做修改,

如"Employee"对应EmployeeEx,那个在Create("Employee")的时候,后台会通过"Employee"找到类型EmployeeEx做实例化。这样的话不管有没有用户自定义列,代码都不用修改。

 

步骤3.在客户端界面自动生成维护控件并绑定,以便用户维护。

        客户端可以新增一个面板控件,用于动态加入控件和绑定字段,如果在步骤1中添加了自定义布局,还可以导入控件布局,再进行绑定。

 

这样,用户就可以不用改到代码就新增自己需要的字段。这对于一个标准产品来说,解决了不同客户的一些简单并且常用的客制需求。

 

一开始提到的Emit应用场景,实现自定义公式解析

举例:用户可以自定义公式,改公式应用于不同员工会得到不同的值。公式内容如下:

公式名称:请假扣款 
    返回类型:数字型
    公式内容:  if 请假次数==0
                return 0;
              else if 请假次数<10
                return 请假扣款系数*请假次数;    
           else        
             return 函数A(请假次数);

 

步骤1.定义系统级参数if,else,return等,

步骤2.定义自定义函数,并提供固定解析方法。(如 decimal 函数A(int)、日期函数等)

步骤3.提供一系列参数,可动态产生。(如  请假次数) 动态编译类型类型请假次数,继承int

步骤4.(最难的一步)将公式解析成动态方法,里面的参数作为方法的变量,遇到函数则使用Emit中的Call。

步骤5.解析公式最后的结果,如员工A,程序运行到公式时,首先通过员工A得到参数的值,即请假扣款系数和请假次数,再把值传入动态方法,最终得到公式的值。

 

当然,具体实做会遇到很多细节上的困难,这里的步骤只是简单的介绍。

 

总结

面对形形色色的用户需求,我们需要做出一些灵活多变的功能,Emit技术为我们解决问题提供了思路。

 

转载于:https://www.cnblogs.com/xiaodlll/p/4029051.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值