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


一個簡易版的T4代碼生成"框架"

對於企業開發來說,代碼生成在某種意義上可以極大地提高開發效率和質量。在眾多代碼生成方案來說,T4是一個不錯的選擇,今天花了點時間寫了一個簡易版本的T4代碼生成的“框架”,該框架僅僅是定義了一些基本的基類以及其他與VS集成相關功能的類型而已。[源代碼從這裏下載]

目錄
一、T4模版的定義和代碼文件的生成
二、TransformationContext與TransformationContextScope
三、Template
四、Generator
五、擴展方法RunCodeGenerator

一、T4模版的定義和代碼文件的生成

我們先來看看最終的代碼生成需要定義那些東西,以及T4模版應該如何定義。對於這個框架來說,代碼結構的生成是通過繼承自我們自定義基類Template的自定義類型實現的。作為演示,我們定義了如下一個DemoTemplate。從代碼可以看出,DemoTemplate僅僅用於生成一個空類,類型名稱在構造函數中指定。

   1:  public class DemoTemplate: Template
   2:  {
   3:      public string ClassName { get; private set; }
   4:      public DemoTemplate(string className)
   5:      {
   6:          this.ClassName = className;
   7:      }
   8:      public override string TransformText()
   9:      {
  10:          this.WriteLine("public class {0}",this.ClassName);
  11:          this.WriteLine("{");
  12:          this.WriteLine("}");
  13:          return this.GenerationEnvironment.ToString();
  14:      }
  15:  }

代碼的生成最終通過執行相應的Generator來實現,為此我們定義了如下一個DemoGenerator。DemoGenerator最終會生成三個.cs文件,而每個文件的代碼最終由上麵定義的DemoTemplate來生成。如下代碼片段所示,繼承自Generator的DemoGenerator重寫了CreateTemplates方法,返回一個字典對象。字典的Key代表生成的文件名,而Value則表示相應的Template對象。

   1:  public class DemoGenerator : Generator
   2:  {
   3:      protected override IDictionary<string, Template> CreateTemplates()
   4:      {
   5:          Dictionary<string, Template> templates = new Dictionary<string, Template>();
   6:          templates.Add("Foo.cs", new DemoTemplate("Foo"));
   7:          templates.Add("Bar.cs", new DemoTemplate("Bar"));
   8:          templates.Add("Baz.cs", new DemoTemplate("Baz"));
   9:          return templates;
  10:      }
  11:  }

最後我們在T4文件中以如下的方式執行DemoGenerator來生成我們需要的三個.cs文件。

   1:  <#@ template hostspecific="true" language="C#" #>
   2:  <#@ assembly name="$(TargetDir)Artech.CodeGeneration.dll" #>
   3:  <#@ import namespace="Artech.CodeGeneration" #>
   4:  <#@ output extension=".empty" #>
   5:  <#
   6:  
   7:  #>

三個.cs文件(Foo.cs、Bar.cs和Baz.cs)最終會以如下的方式生成出來。

Untitled

二、TransformationContext與TransformationContextScope

接下來我們來簡單看看Generator最終是如何利用Template生成相應的文本文件的,不過在這之前我們先來了解一下TransformationContext與TransformationContextScope這兩個類型。顧名思義,TransformationContext用於存儲T4文本轉換的上下文信息,而TransformationContextScope用於限製TransformationContext的作用範圍,這與Transaction/TransactionScope的關係一樣。

TransformationContext定義如下,靜態屬性Current表示當前的TransformationContext,通過它可以得到當前的TextTransformation (即T4文件本身對應的那個TextTransformation 對象),當前的TextTemplatingEngineHost,以及針對T4文件的DTE和ProjectItem。

   1:  public class TransformContext
   2:  {
   3:      public static TransformContext Current { get; internal set; }
   4:      public TextTransformation Transformation{get; private set;}
   5:      public ITextTemplatingEngineHost Host {get; private set;}
   6:      public DTE Dte { get; private set; }
   7:      public ProjectItem TemplateProjectItem { get; private set; }
   8:   
   9:      internal TransformContext(TextTransformation transformation, ITextTemplatingEngineHost host)
  10:      {           
  11:          this.Transformation = transformation;
  12:          this.Host = host;
  13:          this.Dte = (DTE)((IServiceProvider)host).GetService(typeof(DTE));
  14:          this.TemplateProjectItem = this.Dte.Solution.FindProjectItem(host.TemplateFile);
  15:      }
  16:   
  17:      public static void EnsureContextInitialized()
  18:      {
  19:          if (null == Current)
  20:          {
  21:              throw new TransformationException("TransformContext is not initialized.");
  22:          }
  23:      }
  24:  }

TransformationContext的構造函數是Internal的,所以不能在外部直接構建,我們通過具有如下定義的TransformationContextScope來創建它並將其作為當前的TransformationContext。TransformationContextScope實現了IDisposable接口,在實現的Dispose方法中當前的TransformationContext被設置為Null。

   1:  public class TransformContextScope: IDisposable
   2:  {
   3:      public TransformContextScope(TextTransformation transformation, ITextTemplatingEngineHost host)
   4:      {
   5:          TransformContext.Current = new TransformContext(transformation, host);
   6:      }
   7:   
   8:      public void Dispose()
   9:      {
  10:          TransformContext.Current = null;
  11:      }
  12:  }

 

三、Template

代碼生成的邏輯實現在繼承自具有如下定義的Template類型中,而它是TextTransformation的子類。Template的核心是Render和RenderToFile方法,前者指將生成的代碼寫入T4文件對應的生成文件中,後者則將內容寫入某個指定的文件之中。Template生成的代碼內容都是通過調用TransformText獲取,在Render方法中直接通過當前TransformContext獲取T4文件本身代表的TextTransformation對象,並調用其Wirte方法進行內容的寫入。

而RenderToFile方法由於涉及到生成新的文件,邏輯就相對複雜一些。它先通過當前TransformContext得到TextTemplatingEngineHost並計算出T4所在的目錄,並最終解析出生成文件最終的路徑。文件的創建和內容的寫入通過調用CreateFile方法實現,如果涉及到Source Control,還需要執行Check Out操作。新創建的文件最終通過代表T4文件的ProjectItem對象添加到Project之中。

   1:  public abstract class Template: TextTransformation
   2:  {
   3:      private bool initialized;
   4:      public override void Initialize()
   5:      {
   6:          base.Initialize();
   7:          initialized = true;
   8:      }
   9:      internal void EnsureInitialized()
  10:      {
  11:          if (!initialized)
  12:          {
  13:              this.Initialize();
  14:          }
  15:      }
  16:      public virtual void Render()
  17:      { 
  18:          TransformContext.EnsureContextInitialized();
  19:          string contents = this.TransformText();
  20:          TransformContext.Current.Transformation.Write(contents);
  21:      }
  22:      public virtual void RenderToFile(string fileName)
  23:      {
  24:              TransformContext.EnsureContextInitialized();
  25:              string directory = Path.GetDirectoryName(TransformContext.Current.Host.TemplateFile);
  26:              fileName = Path.Combine(directory, fileName);
  27:              string contents = this.TransformText();
  28:              this.CreateFile(fileName, contents);
  29:              if (TransformContext.Current.TemplateProjectItem.ProjectItems.Cast<ProjectItem>().Any(item => item.get_FileNames(0) != fileName))
  30:              {
  31:                  TransformContext.Current.TemplateProjectItem.ProjectItems.AddFromFile(fileName);
  32:              }
  33:      }
  34:      protected void CreateFile(string fileName, string contents)
  35:      {
  36:          if (File.Exists(fileName) && File.ReadAllText(fileName) == contents)
  37:          {
  38:              return;
  39:          }
  40:          SourceControl sourceControl = TransformContext.Current.Dte.SourceControl;
  41:          if (null != sourceControl && sourceControl.IsItemUnderSCC(fileName) && !sourceControl.IsItemCheckedOut(fileName))
  42:          {
  43:              sourceControl.CheckOutItem(fileName);
  44:          }
  45:          File.WriteAllText(fileName, contents);
  46:      }
  47:  }

 

四、Generator

T4文件中最終是通過執行Generator對象來生成代碼的,如下是這個抽象類型的定義。它定義了兩個虛方法,其中CreateTemplates方法一組基於獨立文件的Template對象,返回字典的Key代表生成文件名稱;CreateTemplate返回直接生成在當前T4文件對應生成文件的Template對象。代碼生成通過調用Run方法來完成,而最終的邏輯定義在虛方法RunCore中。

在RunCore方法中,先便利通過CreateTemplates方法返回的Template對象並調用其RenderToFile進行獨立文件的代碼生成,然後調用CreateTemplate方法返回的Template對象的Render方法將代碼生成於默認的代碼文件中。最終執行RemoveUnusedFiles用於生成無用的文件。比如T4文件原來生成Foo.cs文件,現在修改T4文件內容使之生成Bar.cs文件,之前的文件應該在T4文件執行之後被刪除。

   1:  public abstract class Generator
   2:  {
   3:      protected virtual IDictionary<string, Template> CreateTemplates()
   4:      {
   5:          return new Dictionary<string, Template>();
   6:      }
   7:      protected virtual Template CreateTemplate()
   8:      {
   9:          return null;
  10:      }
  11:      public void Run()
  12:      {
  13:          this.RunCore();
  14:          this.RemoveUnusedFiles();
  15:      }
  16:      protected virtual void RunCore()
  17:      {
  18:          List<string> files = new List<string>();
  19:          string directory = Path.GetDirectoryName(TransformContext.Current.Host.TemplateFile);
  20:          foreach (var item in this.CreateTemplates())
  21:          {
  22:              files.Add(Path.Combine(directory, item.Key));
  23:              item.Value.EnsureInitialized();
  24:              item.Value.RenderToFile(item.Key);
  25:          }
  26:   
  27:          Template template = this.CreateTemplate();
  28:          if (null != template)
  29:          {
  30:              template.EnsureInitialized();
  31:              template.Render();
  32:          }
  33:      }
  34:      protected virtual void RemoveUnusedFiles()
  35:      {
  36:          List<string> files = new List<string>();
  37:          string directory = Path.GetDirectoryName(TransformContext.Current.Host.TemplateFile);
  38:          foreach (var item in this.CreateTemplates())
  39:          {
  40:              files.Add(Path.Combine(directory, item.Key));
  41:              item.Value.EnsureInitialized();
  42:              item.Value.RenderToFile(item.Key);
  43:          }
  44:   
  45:          if (null != this.CreateTemplate())
  46:          {
  47:              string defaultCodeFileName = Directory.GetFiles(directory)
  48:                  .Except(new string[] { TransformContext.Current.Host.TemplateFile })
  49:                  .FirstOrDefault(path => Path.GetFileNameWithoutExtension(path) 
  50:                      == Path.GetFileNameWithoutExtension(TransformContext.Current.Host.TemplateFile));
  51:              if (!string.IsNullOrEmpty(defaultCodeFileName))
  52:              {
  53:                  files.Add(defaultCodeFileName);
  54:              }
  55:          }
  56:   
  57:          var projectItems = TransformContext.Current.TemplateProjectItem.ProjectItems.Cast<ProjectItem>().ToArray();
  58:          foreach (ProjectItem projectItem in projectItems)
  59:          {
  60:              string fileName = projectItem.get_FileNames(0);
  61:              if (!files.Contains(fileName))
  62:              {
  63:                  projectItem.Delete();
  64:              }
  65:          }
  66:      }
  67:  }

五、擴展方法RunCodeGenerator

在我們的實例演示中,T4文件中執行Generator是通過調用方法RunCodeGenerator來實現的,這是一個針對TextTransformation的擴展方法。如下麵的代碼片段所示,方法先根據指定的TextTransformation 和TextTemplatingEngineHost 創建當前TransformContext,對Generator 的Run方法的調用是在當前TransformContext中完成的。

   1:  public static class TextTransformationExtensions
   2:  {
   3:      public static void RunCodeGenerator(this TextTransformation transformation, ITextTemplatingEngineHost host, Generator generator)
   4:      {
   5:          using (TransformContextScope contextScope = new TransformContextScope(transformation, host))
   6:          {
   7:              generator.Run();
   8:          }
   9:      }
  10:  }

 

相關閱讀:
與VS集成的若幹種代碼生成解決方案[博文匯總(共8篇)]
通過CodeDOM定義生成代碼的結構
通過Visual Studio的Custom Tool定義代碼生成器
不同於CodeDOM的代碼生成機製——T4
通過T4模板實現單文件的代碼生成
通過T4模板實現多文件的代碼生成
解決T4模板的程序集引用的五種方案
編寫T4模板進行代碼生成無法避免的兩個話題:"Assembly Locking"&"Debug"
通過自定義BuildProvider為ASP.NET提供代碼生成


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

最後更新:2017-10-25 16:04:04

  上一篇:go  MVVM(Knockout.js)的新嚐試:多個Page,一個ViewModel
  下一篇:go  yield在WCF中的錯誤使用——99%的開發人員都有可能犯的錯誤[上篇]