從數據到代碼—通過代碼生成機制實現強類型編程[上篇]

作者: Artech  來源: 博客園  發布時間: 2010-10-24 22:41  閱讀: 1049 次  推薦: 0   原文鏈接   [收藏]  

  我不知道大家對CodeDOM的代碼生成機制是否熟悉,但是有一點可以確定:如果你使用過Visual Studio,你就應該體驗過它帶給我們在編程上的便利。隨便列舉三種典型的代碼生成的場景:在創建強類型DataSet的時候,VS會自動根據Schema生成相應的C#或者VB.NET代碼;當我們編輯Resource文件的時候,相應的的后臺代碼也會自動生成;當我們通過添加Web Reference調用Web Service或者WCF Service的時候,VS會自動生成服務代理的代碼和相應的配置。總的來說,通過和VS集成的動態代碼生成工具使我們可以“強類型”的方式進行編程,進而提供我們的效率并減低錯誤的幾率。

  實際上,除了VS提供的這些典型的代碼生成場景中,我們可以根據需要開發一些自定義代碼生成器,并且通過VS的擴展實現后臺代碼的實時生成,從而實現強類型編程的目的,現在我們舉一個典型的應用場景——消息管理。

  一、一個典型的自定義代碼生成器應用場景——消息管理

  無論對于怎么樣的應用,我們都需要維護一系列的消息。消息的類型很多,比如驗證消息、確認消息、日志消息等。我們一般會將消息儲存在一個文件或者數據庫中進行維護,并提供一些API來獲取相應的消息項。這些API一般都是基于消息的ID來獲取的,換句話說,消息獲取的方式是以一種“弱類型”的編程方式實現的。如果我們能夠根據消息存儲的內容動態地生成相應的C#或者VB.NET代碼,那么我們就能夠以一種強類型的方式來獲取相應的消息項了。

  比如說,現在我們定義了如下一個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:     public MessageEntry(string id, string value, string category)
   7:     {
   8:         this.Id         = id;
   9:         this.Value      = value;
  10:         this.Category   = category;
  11:     }
  12:     public string Format(params object[] args)
  13:     {
  14:         return string.Format(this.Value, args);
  15:     }
  16: }

  現在我們所有的消息定義在如下一個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。我們需要通過我們的代碼生成工具生成一個包含如下C#代碼的CS文件。

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

  那么我們就能夠直接通過生成出來的Messages類,以強類型的方式獲取并格式化每一條MessageEntry的內容了。

   1: Console.WriteLine(Messages.Validation.MandatoryField.Format("User Name"));
   2: Console.WriteLine(Messages.Validation.GreaterThan.Format("Age",18));
   3: Console.WriteLine(Messages.Confirmation.ReallyDelete.Format("Order record"));

  下面是輸出結果:

   1: The User Name is mandatory.
   2: The Age must be greater than 18.
   3: Do you really want to delete the Order record.

  要實現上面的功能實際上包含兩個步驟:一是動態解析包含消息定義的XML文件,并生成我們希望結構的一個代碼定義,而是通過和VS進行集成,借助VS自定義工具將前面生成的內容真正寫入到一個具體的.cs文件中。第一個步驟可以通過CodeDOM輕松實現,而第二個步驟借助于VS的擴展也會很簡單。本篇文章我們只關注第一個方面,下面我們在對第二個方面進行介紹。

  二、通過CodeDom實現動態代碼生成

  CodeDOM 提供了表示許多常見的源代碼元素類型的類型。您可以設計一個生成源代碼模型的程序,使用CodeDOM 元素構成一個對象圖。而這個對象圖包含C#或者VB.NET代碼包含的基本元素:命名空間、類型、類型成員(方法、屬性、構造函數、事件等),并且包括方法實現的具體語句(Statement)。也就是說它的結構就是對一個具體.vb或者.cs文件代碼的反映。在這里我不會具體介紹CodeDOM體系結構,有興趣的讀者可以參與MSDN官方文檔。

  CodeDOM最終體現出來的是一個叫做CodeCompileUnit對象,這個對象通過如下定義的MessageCodeGnerator的BuildCodeObject方法返回。下面給出了生成CodeCompileUnit的全部實現,即使你對CodeDOM完全不了解,結合上面給出的保存消息的XML和我們最終期望的C#代碼的結構,相信也能夠看懂整個實現邏輯。

  總的來說,BuildCodeObject方法的目的就是一個將XML轉換成CodeCompileUnit對象。首先在BuildCodeObject方法中,添加了一個命名空間(Artech.CodeDomGenerator),并在該命名空間中定義了一個Messages的類。在Messages類會為每一個消息類別定義一個嵌套類,類型的名稱就是消息類別的名稱(比如Validation、Confirmation等)。我們具體的MessageEntry通過公共靜態屬性的形式進行定義,并且采用Inline的方式進行初始化。

 
1: public class MessageCodeGenerator
2: {
3: public CodeCompileUnit BuildCodeObject(XmlDocument messages)
4: {
5: var codeObject = new CodeCompileUnit();
6: var codeNamespace = new CodeNamespace("Artech.CodeDomGenerator");
7: codeObject.Namespaces.Add(codeNamespace);
8: var codeType = new CodeTypeDeclaration("Messages");
9: codeNamespace.Types.Add(codeType);
10: GenerateCatetoryClasses(codeType, messages);
11: return codeObject;
12: }
13:
14: private void GenerateCatetoryClasses(CodeTypeDeclaration root, XmlDocument messageDoc)
15: {
16: var messageEntries = messageDoc.GetElementsByTagName("message").Cast<XmlElement>();
17: var categories = (from element in messageEntries
18: select element.Attributes["category"].Value).Distinct();
19:
20: foreach (var category in categories)
21: {
22: var categoryType = new CodeTypeDeclaration(category);
23: root.Members.Add(categoryType);
24:
25: foreach (var element in messageDoc.GetElementsByTagName("message").Cast<XmlElement>().
26: Where(element => element.Attributes["category"].Value == category))
27: {
28: GenerateMessageProperty(element, categoryType);
29: }
30: }
31: }
32:
33: private void GenerateMessageProperty(XmlElement messageEntry, CodeTypeDeclaration type)
34: {
35: string id = messageEntry.Attributes["id"].Value;
36: string value = messageEntry.Attributes["value"].Value;
37: string categotry = messageEntry.Attributes["category"].Value;
38:
39: var field = new CodeMemberField(typeof(MessageEntry), id);
40: type.Members.Add(field);
41: field.Attributes = MemberAttributes.Public | MemberAttributes.Static;
42: field.InitExpression = new CodeObjectCreateExpression(
43: typeof(MessageEntry),
44: new CodePrimitiveExpression(id),
45: new CodePrimitiveExpression(value),
46: new CodePrimitiveExpression(categotry));
47: }
48: }

  三、通過CodeDomProvider轉化給予某種語言的代碼

  CodeCompileUnit最終體現的代碼的結構,但是CodeCompileUnit本身是不基于某種具體的編程語言的,也就是說CodeCompileUnit是語言中性的。最終我們需要另一個對象將CodeCompileUnit轉換成基于某種編程的語言的代碼:CodeDomProvider

  在上面的代碼中,我們利用上面定義的MessageCodeGenerator類型,將上述我們提到的包含消息定義的XML文件轉換成CodeDomProvider對象。最終通過CodeDomProvider將其分別轉換成C#代碼和VB。NET代碼。

   1: var generator = new MessageCodeGenerator();
   2: var messageDoc = new XmlDocument();
   3: messageDoc.Load("Messages.xml");
   4: var codeObject = generator.BuildCodeObject(messageDoc);
   5: CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
   6: CodeGeneratorOptions options = new CodeGeneratorOptions();
   7: using (StreamWriter writer = new StreamWriter("messages.cs"))
   8: {
   9:     provider.GenerateCodeFromCompileUnit(codeObject, writer, options);               
  10: }
  11:  
  12: provider = CodeDomProvider.CreateProvider("VisualBasic");
  13: using (StreamWriter writer = new StreamWriter("messages.vb"))
  14: {
  15:     provider.GenerateCodeFromCompileUnit(codeObject, writer, options);
  16: }
  17:  
  18: Process.Start("messages.cs");
  19: Process.Start("messages.vb");

  這是C#代碼(和我們開始提到過的完全一致):

   1: //------------------------------------------------------------------------------
   2: // <auto-generated>
   3: //     This code was generated by a tool.
   4: //     Runtime Version:4.0.30319.1
   5: //
   6: //     Changes to this file may cause incorrect behavior and will be lost if
   7: //     the code is regenerated.
   8: // </auto-generated>
   9: //------------------------------------------------------------------------------
  10:  
  11: namespace Artech.CodeDomGenerator {
  12:     
  13:     
  14:     public class Messages {
  15:         
  16:         public class Validation {
  17:             
  18:             public static Artech.CodeDomGenerator.MessageEntry MandatoryField = new Artech.CodeDomGenerator.MessageEntry("MandatoryField", "The {0} is mandatory.", "Validation");
  19:             
  20:             public static Artech.CodeDomGenerator.MessageEntry GreaterThan = new Artech.CodeDomGenerator.MessageEntry("GreaterThan", "The {0} must be greater than {1}.", "Validation");
  21:         }
  22:         
  23:         public class Confirmation {
  24:             
  25:             public static Artech.CodeDomGenerator.MessageEntry ReallyDelete = new Artech.CodeDomGenerator.MessageEntry("ReallyDelete", "Do you really want to delete the {0}.", "Confirmation");
  26:         }
  27:     }
  28: }

  下面是VB.NET代碼:

   1: '------------------------------------------------------------------------------
   2: ' <auto-generated>
   3: '     This code was generated by a tool.
   4: '     Runtime Version:4.0.30319.1
   5: '
   6: '     Changes to this file may cause incorrect behavior and will be lost if
   7: '     the code is regenerated.
   8: ' </auto-generated>
   9: '------------------------------------------------------------------------------
  10:  
  11: Option Strict Off
  12: Option Explicit On
  13:  
  14:  
  15: Namespace Artech.CodeDomGenerator
  16:     
  17:     Public Class Messages
  18:         
  19:         Public Class Validation
  20:             
  21:             Public Shared MandatoryField As Artech.CodeDomGenerator.MessageEntry = New Artech.CodeDomGenerator.MessageEntry("MandatoryField", "The {0} is mandatory.", "Validation")
  22:             
  23:             Public Shared GreaterThan As Artech.CodeDomGenerator.MessageEntry = New Artech.CodeDomGenerator.MessageEntry("GreaterThan", "The {0} must be greater than {1}.", "Validation")
  24:         End Class
  25:         
  26:         Public Class Confirmation
  27:             
  28:             Public Shared ReallyDelete As Artech.CodeDomGenerator.MessageEntry = New Artech.CodeDomGenerator.MessageEntry("ReallyDelete", "Do you really want to delete the {0}.", "Confirmation")
  29:         End Class
  30:     End Class
  31: End Namespace

  在《下篇》中,我們將著重介紹如果通過VS的擴展實現如何將我們的MessageCodeGenerator和XML進行綁定,使XML內容改變的時候,相應的代碼能夠動態的生成。 

0
0
 
標簽:.NET
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()