通过Emit实现动态类生成
2024-07-21 02:24:36
供稿:网友
动态生成一个类对于aop,o/r mapping等技术非常有帮助。对于java来说,问题不大,而对于.net,则要麻烦些(主要麻烦在于实现代码的生成需要il),故猜测这可能也是在aop, o/r mapping方面,java走得略前的原因吧。
麻烦归麻烦,非不能也,动态生成一个简单的类还不至于太难。
假设有如下接口:
interface ianimal
{
void move();
void eat();
}
希望能创建一个类生成器typecreator,并能以以下方式使用:
typecreator tc=new typecreator(typeof(ianimal));
type t = tc.build();
ianimal myanimal= (ianimal)activator.createinstance(t);
myanimal.move();
myanimal.eat();
首先,发现system.reflection.emit.typebuilder似乎就是一个现成的类生成器。 不过typebuilder既没有实用的static方法,也不能在外部实例化。不过modulebuilder倒有一个definetype()方法,可以得到typebuilder;而modulebuilder和typerbuilder一个德行,不能直接创建,得从assemblybuilder的definedynamicmodule()方法得到。追根溯源,assemblybuilder得从appdomain的definedynamicassembly()的得来。最终好在appdomain提供了一个静态方法:appdomain.currentdomain. 这一连串并非没有道理,类型是依附于module的,而module依附于assembly,而assembly则被appdomain装载。所谓“皮之不存,毛将焉附”,为了创建type这个“毛”,得先把assembly,module这些“皮”依次构造出来:
using system;
using system.reflection;
using system.reflection.emit;
public class typecreator
{
private type targettype;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="targettype">被实现或者继承的类型</param>
public typecreator(type targettype)
{
this.targettype = targettype;
}
public type build()
{
//获取当前appdomain
appdomain currentappdomain = appdomain.currentdomain;
//system.reflection.assemblyname 是用来表示一个assembly的完整名称的
assemblyname assyname = new assemblyname();
//为要创建的assembly定义一个名称(这里忽略版本号,culture等信息)
assyname.name = "myassyfor_" + targettype.name;
//获取assemblybuilder
//assemblybuilderaccess有run,save,runandsave三个取值
assemblybuilder assybuilder = currentappdomain.definedynamicassembly(assyname,assemblybuilderaccess.run);
//获取modulebuilder,提供string参数作为module名称,随便设一个
modulebuilder modbuilder = assybuilder.definedynamicmodule("mymodfor_"+targettype.name);
//新类型的名称:随便定一个
string newtypename = "imp_"+targettype.name;
//新类型的属性:要创建的是class,而非interface,abstract class等,而且是public的
typeattributes newtypeattribute = typeattributes.class | typeattributes.public;
//声明要创建的新类型的父类型
type newtypeparent;
//声明要创建的新类型要实现的接口
type[] newtypeinterfaces;
//对于基类型是否为接口,作不同处理
if(targettype.isinterface)
{
newtypeparent = null;
newtypeinterfaces = new type[]{targettype};
}
else
{
newtypeparent = targettype;
newtypeinterfaces = new type[0];
}
//得到类型生成器
typebuilder typebuilder = modbuilder.definetype(newtypename,newtypeattribute,newtypeparent,newtypeinterfaces);
//以下将为新类型声明方法:新类型应该override基类型的所以virtual方法
//得到基类型的所有方法
methodinfo[] targetmethods = targettype.getmethods();
//遍历各个方法,对于virtual的方法,获取其签名,作为新类型的方法
foreach(methodinfo targetmethod in targetmethods)
{
//只挑出virtual的方法
if(targetmethod.isvirtual)
{
//得到方法的各个参数的类型
parameterinfo[] paraminfo = targetmethod.getparameters();
type[] paramtype = new type[paraminfo.length];
for(int i=0;i<paraminfo.length;i++)
paramtype[i] = paraminfo[i].parametertype;
//传入方法签名,得到方法生成器
methodbuilder methodbuilder = typebuilder.definemethod(targetmethod.name,methodattributes.public|methodattributes.virtual,targetmethod.returntype,paramtype);
//由于要生成的是具体类,所以方法的实现是必不可少的。而方法的实现是通过emit il代码来产生的
//得到il生成器
ilgenerator ilgen = methodbuilder.getilgenerator();
//以下三行相当于:{console.writeln("i'm "+ targetmethod.name +"ing");}
ilgen.emit(opcodes.ldstr,"i'm "+ targetmethod.name +"ing");
ilgen.emit(opcodes.call,typeof(console).getmethod("writeline",new type[]{typeof(string)}));
ilgen.emit(opcodes.ret);
}
}
//真正创建,并返回
return(typebuilder.createtype());
}
}
好了,测试一下试试看:using system;
public class tester
{
public static void main(string[] args)
{
typecreator tc=new typecreator(typeof(ianimal));
type t = tc.build();
ianimal animal= (ianimal)activator.createinstance(t);
animal.move();
animal.eat();
console.read ();
}
}
得到输出:i'm moveingi'm eating 总结:如果用于aop的话,emit可以动态生成一个装饰类,相比于基于remoting架构的tp/rp的方法,效率可能要高些,而且还能拦截new操作符。缺点:对于非virtual的方法,似乎无法拦截。用于o/r mapping的类生成,倒是不错