通過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的類生成,倒是不錯