不能確定動態代碼編譯在什么地方是有意義的?一個普通情況就應該可以幫助闡明這個問題。假如你不得不從一個數據庫中取出數據并將它放入另一個數據庫。你應該只需使用一個sql語句從源數據庫中選取數據并插入目標數據庫中,這只是小菜一碟,對不對?如果你正在拷貝生產數據以生成測試數據并需要改變數據以確保目標數據在以后開發中使用是安全的又將如何?你可能會構建一個數據傳輸系統(dts)或某個其它傳輸機制,但是如果你這樣做超過足夠多的數據,這就會變成你每次為拷貝數據建立數據-擦除(data-scrubbing)機制而消耗時間。你可以寫一個應用程序來加工并生成測試數據,但是每次你在一個不同的應用程序上用它時你都將不得不改動(應用程序)并創建新的算法。
走進動態代碼編譯。勝于不停地寫一些一次性的代碼,你可以創建一個有特定內部運作機制的應用程序來傳送數據并在傳送時運用代碼段來改變數據。該代碼段將代理每個你需要在數據上要做的動作。它們將被作為原始文本被儲存在一個數據庫中或某個它們可以很容易被修改的其它位置。代碼段將被編譯并在執行時同時應用到數據。這將允許你獲得一個完全是不同的代碼段的數據庫,使得你可以很容易地恢復、修改并應用它而不用每次都要改變你的應用程序的根本。
這是個相當復雜的情況,但是它應該幫助你理解一些可能性。現在,讓我們看看如何實現它。
codecompileunit(代碼編譯單元)
為了動態編譯一個類,從system.codedom命名空間的一個codecompileunit開始。codecompileunit包含一個程序圖形。為了構建代碼,你要創建一些支撐對象并將它們添加到codecompileunit實例中去。這些對象:代表應該已經在你的代碼中的,就像你準備在設計時要創建它的普通對象。
. codenamespace—代表指定的命名空間
. codetypedeclaration—代表類型聲明
. codemembermethod—代表一個方法
一個helloworld例子
你可以使用下面的示例代碼來生成包含一個接收單個參數并返回一個值的sayhello方法的代碼。scriptbody方法的參數值成為sayhello方法的實體(body)。你將你的代碼包含在接收影響結果的參數的一個static(靜態)類中以創建codecompileunit。
public static codecompileunit createexecutionclass(string typenamespace,
string typename,
string scriptbody)
{
// 創建codecompileunit以包含代碼
codecompileunit ccu = new codecompileunit(); // 分配需要的命名空間
codenamespace cns = new codenamespace(typenamespace);
cns.imports.add(new codenamespaceimport("system"));
ccu.namespaces.add(cns); // 創建新的類聲明
codetypedeclaration parentclass = new codetypedeclaration(typename);
cns.types.add(parentclass); // 創建獲得一個參數并返回一個字符串的sayhello方法
codemembermethod method = new codemembermethod();
method.name = "sayhello";
method.attributes = memberattributes.public;
codeparameterdeclarationexpression arg = new codeparameterdeclarationexpression(typeof(string), "inputmessage");
method.parameters.add(arg);
method.returntype = new codetypereference(typeof(string)); // 添加方法實體需要的代碼
codesnippetstatement methodbody =new codesnippetstatement(scriptbody);
method.statements.add(methodbody);
parentclass.members.add(method); return ccu;
}
codeprovider(代碼提供者)
現在你已經創建了一個codecompileunit包含你的代碼段,使用它來生成被編譯到你的動態程序集中去的全部源代碼。下面的靜態方法首先從前面的例子中調用方法并且同時使用csharpcodeprovider生成全部代碼:
public static string generatecode(string typenamespace,
string typename,
string scriptbody)
{
// 調用我們前面的方法創建codecompileunit
codecompileunit ccu = createexecutionclass(typenamespace,
typename, scriptbody); csharpcodeprovider provider = new csharpcodeprovider();
codegeneratoroptions options = new codegeneratoroptions();
options.blanklinesbetweenmembers = false;
options.indentstring = "/t"; stringwriter sw = new stringwriter();
try
{
provider.generatecodefromcompileunit(ccu, sw, options);
sw.flush();
}
finally
{
sw.close();
} return sw.getstringbuilder().tostring();
}
作為一個例子,用輸入值:"codeguru.dynamiccode","scripttype",和"return inputmessage;"調用generatecode方法得出以下輸出:
//---------------------------------------------------------------
// <auto-generated>
// 該代碼是由工具生成的。
// 運行時版本:2.0.50630.0
// 更改這個文件可能導致不正確的(程序)動作并且如果代碼被再次生成時將會丟掉這些更改。
// </auto-generated>
//---------------------------------------------------------------
namespace codeguru.dynamiccode {
using system;
public class scripttype {
public virtual string sayhello(string inputmessage) {
return inputmessage;
}
}
}
在內存中編譯
最后一步是獲得生成的源代碼并將它編譯到一個當前的程序集中去。對于這個例子,你是將這個例子裝入內存而不是一個物理文件。通過特定的編程語言提供者執行當前編譯動作,在這個例程中就是csharpcodeprovider。你設定任何預定的編譯選項并從源代碼編譯這個程序集。
下面的示例代碼從你已構建的代碼中生成了一個程序集:
static assembly compileinmemory(string code)
{
csharpcodeprovider provider = new csharpcodeprovider(); compilerparameters options = new compilerparameters();
options.includedebuginformation = false;
options.generateexecutable = false;
options.generateinmemory = true;
compilerresults results =provider.compileassemblyfromsource(options, code);
provider.dispose();
assembly generatedassembly = null;
if (results.errors.count == 0)
{
generatedassembly = results.compiledassembly;
}
return generatedassembly;
}
如assembly a = compileinmemory(generatecode(typenamespace, typename, "return inputmessage;"));的調用將會生成一個新的程序集。你可能會用任何你想要的方法實體代替"return inputmessage;"來創建預定的變量作些并發調用。
創建一個實例
你已經動態生成了一個程序集并將其編譯到內存中。下一個任務就是從程序集中創建一個類的實例。這實際上比聽起來更加復雜。你已經創建的程序集存在于內存中。對它的存在沒有任何參考信息,因此你不能簡單的創建一個新的實例,因為它們不會解決問題。創建一個類以擁有所有已編譯程序集作為一個工作區。你將不顧類型決定事件,所以當一個類型需要時你可以使用你的類型中的一個。 executionhost示例代碼
下面的代碼定義了一個名為executionhost的類,它追蹤了你所有的動態編譯程序集:
using system;
using system.collections;
using system.reflection;
namespace codeguru.codedomsample
{
class executionhost
{
private hashtable assemblies = null;
public executionhost()
{
assemblies = new hashtable();
// 響應類型解析事件(the type resolution event)要求以截取它并找到我們類型
appdomain.currentdomain.typeresolve += new resolveeventhandler(currentdomain_typeresolve);
}
private assembly currentdomain_typeresolve(object sender,resolveeventargs args)
{
// 為預定的類型找出我們程序集
assembly a = null;
if (assemblies.containskey(args.name))
{
a = (assembly)assemblies[args.name];
}
return a;
} public void addassembly(string fulltypename, assembly a)
{
assemblies.add(fulltypename, a);
} public string execute(string typefullname, string msg)
{
// 嘗試創建觸發事件所需要的類型
type targettype = type.gettype(typefullname, true, true);
object target =targettype.assembly.createinstance(typefullname);
iexecutablemodule m = (iexecutablemodule)target; return m.sayhello(msg);
}
}
}
namespace codeguru.codedomsample
{
public interface iexecutablemodule
{
string sayhello(string inputmessage);
}
}
public static codecompileunit createexecutionclass(string typenamespace,string typename,string scriptbody)
{
// 創建codecompileunit以存放代碼
codecompileunit ccu = new codecompileunit(); // 分配給預期的命名空間
codenamespace cns = new codenamespace(typenamespace);
cns.imports.add(new codenamespaceimport("system"));
ccu.namespaces.add(cns);
// 創建類
codetypedeclaration parentclass = new codetypedeclaration(typename);
cns.types.add(parentclass);
// 新行-為iexecutablemodule接口添加一個實現
parentclass.basetypes.add(typeof(codeguru.codedomsample.iexecutablemodule));
// 創建獲得一個參數并返回一個字符串的sayhello方法
codemembermethod method = new codemembermethod();
method.name = "sayhello";
method.attributes = memberattributes.public;
codeparameterdeclarationexpression arg = new codeparameterdeclarationexpression(typeof(string),
"inputmessage");
method.parameters.add(arg);
method.returntype = new codetypereference(typeof(string));
// 添加預期代碼到方法實體
codesnippetstatement methodbody = new codesnippetstatement(scriptbody);
method.statements.add(methodbody);
parentclass.members.add(method);
return ccu;
}
注意execute方法。它用反射來創建預定類型的一個實例。這將觸發_typeresolve事件并允許你的程序集中的一個被返回,如果該被返回程序集通過addassembly方法已被添加到executionhost中了。
你也要注意在你的動態生成代碼中添加的接口實現。沒有它,你將不知道如何調用預期的方法。為了你的生成代碼,iexecutablemodule接口與作為一個基類添加的附加接口的createexecutionclass方法的一個最新副本一同被提供。
另外,因為你增添了一個現在需要在codeguru.dynamiccode程序集內部使用的接口,你必須給含有iexecutablemodule 聲明的codeguru.codedomsample添加一個接口。請看下面最新的compileinmemory副本:
static assembly compileinmemory(string code)
{
csharpcodeprovider provider = new csharpcodeprovider();
compilerparameters options = new compilerparameters();
options.includedebuginformation = false;
options.generateexecutable = false;
options.generateinmemory = true;
// 新行-添加一個接口到需要的程序集
options.referencedassemblies.add("codeguru.codedomsample.exe");
compilerresults results = provider.compileassemblyfromsource(options, code);
provider.dispose(); assembly generatedassembly = null;
if (results.errors.count == 0)
{
generatedassembly = results.compiledassembly;
} return generatedassembly;
}
現在,你可以用下面的測試代碼來測試動態生成一個程序集然后對方法做一個調用的端到端(end-to-end)過程:
string typenamespace = "codeguru.dynamiccode";
string typename = "scripttype" + guid.newguid().tostring("n");
assembly a = compileinmemory(generatecode(typenamespace, typename,"return inputmessage;"));
executionhost host = new executionhost();
string fulltypename = typenamespace + "." + typename;
host.addassembly(fulltypename, a);
string test = host.execute(fulltypename, "hello world!");
每次在你生成代碼時使用guid生成唯一對象名稱。
后記
你已看完了一個非?;镜睦?,它描述了一個復雜的主題及完成這個任務所需要的代碼。在類型名稱上添加guid是為了確保其唯一性,因此你可以隨心所欲地編譯并使用各種不同的類型而不會在名稱上發生沖突。你可以自由改變“return inputmessage”方法實體成為任何你喜歡的代碼并試用之。你可以改變它,以使得所有關于方法實體的代碼被存儲在一個數據庫中并在運行時重新獲得。