一個簡易版的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 IDictionaryCreateTemplates()
4: {
5: Dictionarytemplates = new Dictionary ();
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:
2:
3:
4:
5:
6: this.RunCodeGenerator(this.Host, new DemoGenerator());
7: #>三個.cs檔案(Foo.cs、Bar.cs和Baz.cs)最終會以如下的方式生成出來。
二、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().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 IDictionaryCreateTemplates()
4: {
5: return new Dictionary();
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: Listfiles = new List ();
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: Listfiles = new List ();
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().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提供程式碼生成
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4729/viewspace-2805195/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 3行程式碼實現一個簡易版promise行程Promise
- 寫一個簡單的程式碼生成器
- 200行程式碼寫一個簡易的dva行程
- 簡易版NFT合約程式碼
- Vue原始碼分析之實現一個簡易版的VueVue原始碼
- 實現一個簡易版WebpackWeb
- 來實現一個簡易版的 PromisePromise
- 裝逼的最高境界---一行js程式碼完成一個簡易版的貪吃蛇遊戲JS遊戲
- 封裝一個簡易版的ajax操作物件封裝物件
- 基於vue搭建一個簡易版豆瓣Vue
- 基於React搭建一個簡易版豆瓣React
- 簡單的程式碼生成工具
- jq的簡易模板生成
- 簡易版抽獎小程式
- 一起來擼個簡易的小程式框架框架
- 編寫一個簡易計時器程式(edu)
- 用Python操作SFTP的簡易程式碼PythonFTP
- 手寫一個簡易的WebpackWeb
- 實現一個簡易的vueVue
- 【Mysql】一個簡易的索引方案MySql索引
- 如何實現一個簡易版的 Spring - 如何實現 AOP(中)Spring
- 如何實現一個簡易版的 Spring - 如何實現 AOP(上)Spring
- 如何實現一個簡易版的 Spring - 如何實現 Setter 注入Spring
- 基於 Mysql 實現一個簡易版搜尋引擎MySql
- 如何搭建一個簡易的 Web Terminal(一)Web
- 統計程式碼行數簡易程式
- 如何實現一個簡易版的 Spring - 如何實現 @Autowired 註解Spring
- 用 Go 寫一個簡易的 dockerGoDocker
- java生成簡易pdf文件Java
- 最簡單的mybatis自動程式碼生成MyBatis
- java版gRPC實戰之一:用proto生成程式碼JavaRPC
- DI 原理解析 並實現一個簡易版 DI 容器
- 程式碼生成工具(一)
- 簡易實現一個expressExpress
- 分享一個簡易部落格
- WebGL簡易教程(一):第一個簡單示例Web
- carbon-一個能生成漂亮的程式碼截圖工具
- 程式碼來構建一個簡單的compilerCompile