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了,反编译的结果如下:

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反编译,结果如下:

3.添加方法
在添加属性的代码中,有一部分是IL语言的反射,估计是Emit中让人觉得难以理解的代码,添加方法的时候就更需要深入理解IL语言的转换,从而实现对方法的动态添加。
例如方法void PropertyChanged(string propertyName),先另外用C#写好代码:
public void PropertyChanged(string propertyName) {
if (propertyName != null) {//判断语句
Console.WriteLine(propertyName + " Changed!");
}
}
然后生成dll,利用反编译工具得到这段代码的IL语言

了解一些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,反编译看看结果:

这样,一个简单的类就产生了,有了类标记、字段、属性、方法。
但是,现实需求不仅如此,我们还有可能需要有继承、接口等,来完善我们的类型。
继承和接口添加
//创建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技术为我们解决问题提供了思路。
Emit技术使用实例及应用思路的更多相关文章
- 用Emit技术替代反射
之前在上篇博客说到用表达式来替代反射机制,可以获得较高的性能提升.这篇我们来说说用Emit技术来替代反射. System.Reflection.Emit命名空间类可用于动态发出Microsoft中间语 ...
- 【转】精选30个优秀的CSS技术和实例
今天,我为大家收集精选了30个使用纯CSS完成的强大实践的优秀CSS技术和实例,您将在这里发现很多与众不同的技术,比如:图片集.阴影效果.可扩展按钮.菜单等-这些实例都是使用纯CSS和HTML实现的. ...
- 精选30个优秀的CSS技术和实例
精选30个优秀的CSS技术和实例 投递人 墙头草 发布于 2008-12-06 20:57 评论(97) 有17487人阅读 原文链接 [收藏] « » 今天,我为大家收集精选了30个使用纯CSS ...
- css技术和实例
今天,我为大家收集精选了30个使用纯CSS完成的强大实践的优秀CSS技术和实例,您将在这里发现很多与众不同的技术,比如:图片集.阴影效果.可扩展按钮.菜单等-这些实例都是使用纯CSS和HTML实现的. ...
- CORBA技术及实例
CORBA技术及实例 CORBA是一种规范,它定义了分布式对象如何实现互操作.在WorldWideWeb盛行之前,非凡是java编程语言风靡之前,C++开发者基本将CORBA作为其高端分布式对象的解决 ...
- 网页编程技术与实例 PDF扫描版
本书主要包括:Web的概念,使用网页编辑工具制作网页,HTML语言的基本结构,JavaScrip和VBScript脚本语言的编程方法,ASP的概念,ASP对象的属性.方法和事件,SQL语言,数据库建议 ...
- 30个优秀的CSS技术和实例 By 彬Go 2008-12-04
在这里可发现很多与众不同的技术,比如:图片集.阴影效果.可扩展按钮.菜单等…这些实例都是使用纯CSS和HTML实现的.单击每个实例的标题可以被转向到该技术实例的相关教程或说明页面(英文),单击每个实例 ...
- 基于VC的串行通信技术应用实例
在工业控制中,串口是常用的计算机与外部串行设备之间的数据传输通道,由于串行通信方便易行,所以应用广泛. 本文将介绍在Windows平台下串行通信的工作机制和用Visual C++设计串行通信程序的 ...
- android中反射技术使用实例
在计算机科学领域.反射是指一类应用,它们能够自描写叙述和自控制.也就是说,这类应用通过採用某种机制来实现对自己行为的描写叙述(self-representation)和监測(examination), ...
随机推荐
- oc随笔五:NSArray
#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { ...
- MFC软件工程架构模型-模式窗口-非模式窗口
1. SDI单文档界面: MDI多文档界面.有多个"关闭-最大化-最小化"等这样的窗口嵌套 基于对话框的软件模型 2.模式对话框和非模式对话框 模式对话框:使用DoMoel(),弹 ...
- php+mysql将大数据sql文件导入数据库
<?php $file_name = "d:test.sql"; $dbhost = "localhost"; $dbuser = "root& ...
- web app之rem
rem是什么? rem:font size of the root element,是指相对于根元素的字体大小的单位.简单的说它就是一个相对单位. em:font size of the elemen ...
- Python 学习日记(第三周)
知识回顾 在上一周的学习里,我学习了一些学习Python的基础知识下面先简短的回顾一些: 1Python的版本和和安装 Python的版本主要有2.x和3.x两个版本这两个版本在语法等方面有一定的区别 ...
- Monkey and Banana(HDU 1069 动态规划)
Monkey and Banana Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others ...
- C语言学习中容易模糊的一些概念
1.什么叫分配内存 操作系统把某一块内存空间的使用权利分配给该程序 2.释放内存 操作系统把分配给该程序的内存空间的使用权利收回,该程序就不能再使用这块内存空间 注:释放内存空间并不是把这块内存的数据 ...
- C语言读写伯克利DB 4
因为缓存数据的buffer总是不够大(会引起段错误)索性从堆上拿了两块大内存 /* 功能说明:逐日存储来访用户(使用伯克利DB) 根据存储的用户信息确定某用户是否是首次来访用户(未被存储的伯克利DB) ...
- 关于php-fpm通讯时没有REQUEST_METHOD的问题
nginx是通过fastcgi协议来和php通讯的!而php-fpm就扮演了这样的角色 fastcgi协议 中文版http://blog.chinaunix.net/uid-380521-id-241 ...
- POJ 2392 Space Elevator DP
该题与POJ 1742的思路基本一致:http://www.cnblogs.com/sevenun/p/5442279.html(多重背包) 题意:给你n个电梯,第i个电梯高h[i],数量有c[i]个 ...