.NET下的動態代碼編譯探索

來源:互聯網
上載者:User
不能確定動態代碼編譯在什麼地方是有意義的?一個普通情況就應該可以協助闡明這個問題。假如你不得不從一個資料庫中取出資料並將它放入另一個資料庫。你應該只需使用一個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”方法實體成為任何你喜歡的代碼並試用之。你可以改變它,以使得所有關於方法實體的代碼被儲存在一個資料庫中並在運行時重新獲得。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.