程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> .NET實例教程 >> .NET 環境下運行時代碼生成和編譯

.NET 環境下運行時代碼生成和編譯

編輯:.NET實例教程

CLR自帶了各種語言的編譯器,例如C#、VB等。通過這些編譯器以及反射,可以實現以前在其它環境中做不到的事情:運行時代碼生成和編譯。

作為一個應用,我們以對象工廠作為示例。對象工廠是通過一些標識符,在運行時生成不同對象的一種設計模式,通常的代碼形式為:


public class ObjectFactory
{
public static object CreateInstance(string
id)
{
switch
(id)
{
case
"A":
return new
A();

case
"B":
return new
B();



default
:
return null
;
}
}

這段代碼非常好,但是存在一個問題是,這是一段源代碼,要想在運行時動態增加可以創建的對象就做不到。一種場景就是需要創建的對象是通過配置文件來確定的,那麼就不能使用這種方式了。

CLR環境下反射的存在可以非常方便地實現動態工廠,例如:


public class ObjectFactory2
{
public static object CreateInstance(string
id)
{
Type type =
Type.GetType(id);

return
Activator.CreateInstance(type);
}
}

這裡要求id是一個CLR類型名稱。看上去很漂亮的代碼,問題是這段代碼非常慢,比前一段代碼慢一萬倍以上--原因在於在.Net中,幾乎所有的反射都會涉及到對虛擬機本身的調用,而這種調用是通過COM進行的。此外還涉及到對整個類型系統的搜索等開銷也是很龐大的。

第三種方案,使用一個Hash表以及配合委托進行。在系統初始化的時候創建一個Hash表,表的鍵為對象標識符,表的值為對象的類型,這樣代碼就會變成:


public class ObjectFactory3
{
public static
Hashtable m_objectTypes;

public static object CreateInstance(string
id)
{
Type type = m_objectTypes(id) as
Type;
return
Activator.CreateInstance(type);
}
}

在這段代碼中,節省了Type.GetType的時間,但是並沒有回避Activator.CreateInstance的代價,並且在大系統中,使用Hash表的效率也不是很高。

那麼有沒有更快、但是更加靈活的方式呢?回答是使用動態代碼生成和編譯技術。

想法是很簡單的,我們還是回到第一種方式,switch語句,只要我們在運行時讀入需要創建的對象標識符和對象類型,然而按照switch語句的語法創建一個C#源代碼文件,然後編譯,就可以了。這裡涉及到幾個問題:

1、 如何書寫源代碼:這實際上是很簡單的,創建一個StringBuilder,然後往裡面寫字符串就可以了。然而為了提高代碼的可讀性和方便性,我們可以對其進行一些封裝,例如下面這個接口可以完成大部分代碼書寫的工作。


public interface ICodeWriter
{
void
WriteLine();
void WriteLine(int
count);
void WriteLine(string
s);
void WriteLine(string s, params object
[] args);
void Write(string
s);
void Write(string s, params object
[] args);
void
CommentLine();
void CommentLine(int
count);
void CommentLine(string
s);
void CommentLine(string s, params object
[] args);
void
Indent();
void Indent(int
count);
void
UnIndent();
void UnIndent(int
count);
void
WriteIndents();
}

2、 如何組織源代碼:基本上,我們需要下面這些信息:
a) 生成的工廠名稱,以及工廠所需要實現的接口;
b) 需要生成的最終對象的類型,在第一個示例中,最終對象的類型是object,然而我們也可以用其它類型來代替,一個比較好的方式是使用一個所有要創建對象的公共基類類型;
c) 對象標識符:這是一個字符串數組,包含所有對象的標識符;
d) 對象類型:這是一個字符串數組或者類型數組,包含所有要創建的對象類型名稱或者類型。

有了這些信息以後,我們就可以編寫這個工廠的創建程序了:


public class ObjectFactoryBuilder
{
public static string CreateObjectFactorySource(string factoryName, string factoryBaseName, string baseProductName, string [] productIds, string
[] productTypes)
{
ICodeWriter writer = new
CSharpWriter();

writer.WriteLine("public classs {0}: {1}", factoryName, factoryBaseName);

writer.Indent();
writer.WriteLine("public {0} CreateInstance(string id)", baseProductName);

writer.Indent();

writer.WriteLine("switch (name)"
);
writer.Indent();
for (int k = 0; k< productsIds.Length; ++
k)
{
Writer. WriteLine("case \"{0}\": return new {1}();", productsIds[k], productTypes[k]);

}
Writer.WriteLine("default: return null;"
);
Writer.Unindent(3
);

return
Writer.ToString();
}
}

上面這段代碼就可以根據傳入的信息,自動生成符合C#語法的源代碼。

3、 如何編譯:在 System.CodeDom.Compiler名字空間中包含了基本的編譯器支持,Microsoft.CSharp名字空間中提供了C#編譯器的實際對象。首先創建一個CompilerParameter對象,設置編譯選項,然後用下面語句創建編譯器並且編譯代碼:


CompilerParameters cp = new CompilerParameters();
// 設置 cp.ReferencedAssemblIEs

CodeDomProvider provider = new CSharpCodeProvider();
CompileResult cr = provider.CompileAssemblyFromSource(cp, source);

其中,source是一個字符串,包含從CreateObjectFactorySource得到的工廠源代碼。如果編譯成功,那麼cr.Errors.Count == 0,編譯生成的配件就是cr.CompiledAssembly。假設我們的工廠名字是ObjectFactory4,實現的接口是IObjectFactory,那麼得到配件後,可以寫:


Assembly asm = cr.CompiledAssembly;
IObjectFactory factory = asm.CreateInstance("ObjectFactory4") as IObjectFactory;

基於這種工作模式,我們需要在我們自己的配件中定義一個基類或者接口IObjectFactory,然後讓動態生成的工廠繼承基類或者實現接口,這樣我們就可以調用這個工廠來創建對象了。

本文出自:http://www.cnblogs.com/xlshcn/archive/2007/11/21/runtimecodegen.Html

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved