閱讀1002 返回首頁    go 阿裏雲 go 技術社區[雲棲]


從數據到代碼——基於T4的代碼生成方式

在之前寫一篇文章《從數據到代碼》(上篇下篇)中,我通過基於的代碼生成方式實現了將一個XML表示的消息列表轉換成了相應的C#代碼,從而達到了的目的。實際上,我們最常用的代碼生成當時不是CodeDOM,而是,這是一個更為強大,並且適用範圍更廣的代碼生成技術。今天,我將相同的例子通過T4的方式再實現一次,希望為那些對T4不了解的讀者帶來一些啟示。同時這篇文章將作為後續文章的引子,在此之後,我將通過兩篇文章通過具體實例的形式講述如果在項目將T4為我所用,以達到提高開發效率和保證質量的目的。[這裏有T4相關的資料][文中的例子可以從這裏下載]

目錄
一、我們的目標是:從XML文件到C#代碼
二、從Hello World講起
三、T4模板的基本結構
四、通過T4模板實現從“數據到代碼”的轉變
五、T4的文本轉化的實現

再次重申一下我們需要通過“代碼生成”需要達到的目的。無論對於怎麼樣的應用,我們都需要維護一係列的消息。消息的類型很多,比如驗證消息、確認消息、日誌消息等。我們一般會將消息儲存在一個文件或者數據庫中進行維護,並提供一些API來獲取相應的消息項。這些API一般都是基於消息的ID來獲取的,換句話說,消息獲取的方式是以一種“”的編程方式實現的。。

比如說,現在我們定義了如下一個MessageEntry類型來表示一個消息條目。為了簡單,我們盡量簡化MessageEntry的定義,僅僅保留三個屬性Id、Value和Category。Category表示該消息條目所屬的類型,你可以根據具體的需要對其分類(比如根據模塊名稱或者Severity等)。Value是一個消息真實的內容,可以包含一些占位符({0},{1},…{N})。通過指定占位符對用的值,最中格式化後的文本通過Format返回。

   1: public class MessageEntry
   2: {
   3:     public string Id { get; private set; }
   4:     public string Value { get; private set; }
   5:     public string Category { get; private set; }
   6:  
   7:     public MessageEntry(string id, string value, string category)
   8:     {
   9:         this.Id         = id;
  10:         this.Value      = value;
  11:         this.Category   = category;
  12:     }
  13:     public string Format(params object[] args)
  14:     {
  15:         return string.Format(this.Value, args);
  16:     }
  17: }

現在我們所有的消息定義在如下一個XML文件中,<message>XML元素代碼一個具體的MessageEntry,相應的屬性(Attribute)和MessageEntry的屬性(Property)相對應。

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <messages> 
   3:   <message id="MandatoryField" value="The {0} is mandatory."  category="Validation"/> 
   4:   <message id="GreaterThan" value="The {0} must be greater than {1}."  category="Validation"/> 
   5:   <message id="ReallyDelete" value="Do you really want to delete the {0}."  category="Confirmation"/>  
   6: </messages>

在上麵的XML中,定義了兩個類別(Validation和Confirmation)的三條MessageEntry。。

   1: public static class Messages
   2: {
   3:     public static class Validation
   4:     {
   5:         public static MessageEntry MandatoryField = new MessageEntry("MandatoryField", "The {0} is mandatory.", "Validation");
   6:         public static MessageEntry GreaterThan = new MessageEntry("GreaterThan", "The {0} must be greater than {1}.", "Validation");
   7:     }
   8:     public static class Confirmation
   9:     {
  10:         public static MessageEntry ReallyDelete = new MessageEntry("ReallyDelete", "Do you really want to delete the {0}.", "Confirmation");
  11:     }
  12: }

那麼如何通過T4的方式來實現從“數據”(XML)到“代碼”的轉換呢?在投入到這個稍微複雜的工作之前,我們先來弄個簡單的。

我們之前一直在講T4,可能還有人不知道T4到底代表什麼。T4是對“”(4個T)的簡稱。T4直接包含在VS2008和VS2010中,是一個基於文本文件轉換的工具包。T4的核心是一個基於“文本模板”的轉換引擎(以下簡稱),我們可以通過它生成一切類型的文本型文件,比如我們常用的代碼文件類型包括:C#、VB.NET、T-SQL、XML甚至是配置文件等。

對於需要通過T4來進行代碼生成工作的我們來說,需要做的僅僅是根據轉換源(Transformation Source),比如數據表、XML等(由於例子簡單,HelloWord模板沒有輸入源)和目標文本(比如最終需要的C#或者T-SQL代碼等)定義相應的模板。T4模板作用就。

T4模板的定義非常簡單,整個模板的內容包括兩種形式:和。前者就是直接寫在模板中作為的文本,後者是基於某種語言編寫代碼,T4引擎會動態執行它們。這和我們通過很相似:HTML是靜態的,以C#或者VB.NET代碼便寫的動態執行的代碼通過相應的標簽內嵌其中。為了讓讀者對T4模板有一個直觀的認識,我們先來嚐試寫一個最簡單的。假設我們需要通過代碼生成的方式生成如下一段簡單的C#代碼:

   1: using System;
   2:  
   3: namespace Artech.CodeGeneration
   4: {
   5:     class Program
   6:     {
   7:         static void Main(string[] args)
   8:         {
   9:             Console.WriteLine("Hello, {0}", "Foo");
  10:             Console.WriteLine("Hello, {0}", "Bar");
  11:             Console.WriteLine("Hello, {0}", "Baz");
  12:         }
  13:     }
  14: }
現在我們直接通過VS來創建一個T4模板來生成我們期望的C#代碼。右擊項目文件,選擇"Add"|"New Item",在模板列表中選擇。指定文件名後確定,一個後綴名為.的文件會被創建,然後在該文件中編寫如下的代碼。
   1: <#@ template debug="false" hostspecific="false" language="C#" #>
   2: <#@ assembly name="System.Core.dll" #>
   3: <#@ import namespace="System" #>
   4: <#@ output extension=".cs" #>
   5: using System;
   6:  
   7: namespace Artech.CodeGeneration
   8: {
   9:     class Program
  10:     {     
  11:         static void Main(string[] args)
  12:         {    
  13:             <#
  14:             foreach(var person in this.InitializePersonList()) 
  15:             {
  16:             #>
  17:                 Console.WriteLine("Hello, {0}","<#=  person#>");
  18:             <#
  19:             } 
  20:             #>
  21:         }
  22:     }
  23: }
  24:  
  25: <#+ 
  26:     public string[] InitializePersonList()
  27:     {
  28:         return new string[]{"Foo","Bar","Baz"};
  29:     }
  30: #>
保存該文件後,一個.cs文件將會作為該TT文件的附屬文件被添加(如右圖所示的HelloWorld.cs)。上述的這個TT文件雖然簡單,卻包含了構成一個T4模板的基本元素。在解讀該T4模板之前,我們有必要先來了解一個完整的T4模板是如何構成的。

假設我們用“塊”(Block)來表示構成T4模板的基本單元,它們基本上可以分成5類:、、、和。

1、指令塊(Directive Block)

和ASP.NET頁麵的指令一樣,它們出現在文件頭,通過表示。其中<#@ template …#>指令是必須的,用於定義模板的基本屬性,比如編程語言、基於的文化、是否支持調式等等。比較常用的指令還包括用於程序集引用的<#@ assembly…#>,用於導入命名空間的<#@ import…#>等等。

2、文本塊(Text Block)

文本塊就是直接原樣輸出的靜態文本,不需要添加任何的標簽。在上麵的模板文件中,處理定義在<#… #>、<#+… #>和<#=… #>中的文本都屬於文本塊。比如在指令塊結束到第一個“<#”標簽之間的內容就是一段靜態的文本塊。

   1: using System;
   2:  
   3: namespace Artech.CodeGeneration
   4: {
   5:     class Program
   6:     {     
   7:         static void Main(string[] args)
   8:         {    
   9:             

3、代碼語句塊(Statement Block)

代碼語句塊通過的形式表示,中間是一段通過相應編程語言編寫的程序調用,我們可以通過代碼語句快控製文本轉化的流程。在上麵的代碼中,我們通過代碼語句塊實現對一個數組進行遍曆,輸出重複的Console.WriteLine(“Hello, {0}”, “Xxx”)語句。

   1: <#
   2: foreach(var person in this.InitializePersonList()) 
   3: {
   4: #>
   5:     Console.Write("Hello, {0}","<#=  person#>");
   6: <#
   7: } 
   8: #>

4、表達式塊(Expression Block)

表達式塊以的形式表示,通過它之際上動態的解析的字符串表達內嵌到輸出的文本中。比如在上麵的foreach循環中,每次迭代輸出的人名就是通過表達式塊的形式定義的(<#=  person#>)

5、類特性塊(Class Feature Block)

如果文本轉化需要一些比較複雜的邏輯,我們需要寫在一個單獨的輔助方法中,甚至是定義一些單獨的類,我們就是將它們定義在類特性塊中。類特性塊的表現形式為對於Hello World模板,得到人名列表的InitializePersonList方法就定義在類特性塊中。

   1: <#+ 
   2:     public string[] InitializePersonList()
   3:     {
   4:         return new string[]{"Foo","Bar","Baz"};
   5:     }
   6: #>

了解T4模板的“五大塊”之後,相信讀者對定義在HelloWord.tt中的模板體現的文本轉化邏輯應該和清楚了吧。

現在我們來完成我們開篇布置得任務:如何將一個已知結構的表示消息列表的XML轉換成C#代碼,使得我們可以一強類型的編程方式獲取和格式化相應的消息條目。我們的T4模板定義如下

   1: <#@ template debug="false" hostspecific="true" language="C#" #>
   2: <#@ assembly name="System.Core.dll" #>
   3: <#@ assembly name="System.Xml" #>
   4: <#@ import namespace="System" #>
   5: <#@ import namespace="System.Xml" #>
   6: <#@ import namespace="System.Linq" #>
   7: <#@ output extension=".cs" #>
   8:  
   9: namespace MessageCodeGenrator
  10: {
  11:     public static class Messages
  12:     {    
  13:         <# 
  14:         XmlDocument messageDoc = new XmlDocument();
  15:         messageDoc.Load(this.Host.ResolvePath("Messages.xml"));
  16:       
  17:         var messageEntries = messageDoc.GetElementsByTagName("message").Cast<XmlElement>();  
  18:         var categories = (from element in messageEntries
  19:                             select element.Attributes["category"].Value).Distinct();
  20:         foreach (var category in categories)  
  21:         {
  22:             #>
  23: public  static class <#=  category#>
  24:             {
  25:                 <#
  26:                 foreach (var element in messageDoc.GetElementsByTagName("message").Cast<XmlElement>().Where(element => element.Attributes["category"].Value == category))  
  27:                 {                      
  28:                     string id           = element.Attributes["id"].Value;  
  29:                     string value        = element.Attributes["value"].Value;  
  30:                     string categotry    = element.Attributes["category"].Value;
  31:                 #>
  32: public static MessageEntry <#= id #> = new MessageEntry("<#= id #>","<#=  value#>","<#=  categotry#>");
  33:             <#  } #>
  34:             }
  35:     <# } #>
  36:     }
  37: }

模板體現出來的轉化流程就是:加載XML文件(Messages.xml),然後獲取所有的消息類別,為每個消息類別創建一個內嵌於靜態類Messages中的以類別命名的類。然後遍曆每個類別下的所有消息條目,定義類型為MessageEntry的靜態熟悉。

在這裏有一點需要特別指出的是:整個代碼生成的輸入,即XML文件Messages.xml和模板文件位於相同的目錄下,但是我們需要通過屬性的方法去解析文件的物理路徑。對ResolvePath方法的調用,需要模板<#@ template …#>指令中的設置為。

   1: <#@ template debug="false" hostspecific="true" language="C#" #>

和我之前采用的代碼生成方式(CodeDOM+Custom Tool)一樣,對於T4模板的代碼生成,VS最終還是通過Custom Tool來完成的。如果你查看TT文件的屬性,你會發現Custom Tool會自動設置成:。

image

當TextTemplatingFileGenerator被觸發後(修改後的文件被保存,或者認為執行Custom Tool),會通過T4引擎完成文本的轉換和輸出工作。具體來講,T4引擎的文本轉化和輸出機製可以通過下圖來表示。T4引擎首先對模板的靜態內容和動態內容進行解析,最終生成一個繼承自Microsoft.VisualStudio.TextTemplating.TextTransformation的類,所有的文本轉化邏輯被放入被重寫的Transformation方法中。然後動態創建該對象,執行該方法並將最終的類型以附加文件的形式輸出來。

 

從數據到代碼——通過代碼生成機製實現強類型編程[上篇]
從數據到代碼——通過代碼生成機製實現強類型編程[下篇]
從數據到代碼——基於T4的代碼生成方式
創建代碼生成器可以很簡單:如何通過T4模板生成代碼?[上篇]
創建代碼生成器可以很簡單:如何通過T4模板生成代碼?[下篇]


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-27 14:04:38

  上一篇:go  關於CLR內存管理一些深層次的討論[下篇]
  下一篇:go  創建代碼生成器可以很簡單:如何通過T4模板生成代碼?[上篇]