一个简易版的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: 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<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: }
- Linux+Nginx/Apache/Tomcat新增SSL证书,开启https访问教程
- golang 使用时间通过md5生成token
- golang中对map操作类
- Nginx在线服务状态下平滑升级或新增模块的详细操作记录
- 【Dev Club分享】微信读书iOS性能优化
- [svn: E155004]svn update报database is locked错误的解决办法
- WordPress高亮插件:Crayon Syntax Highlighter加载优化
- 深入理解 ButterKnife,让你的程序学会写代码
- JS+CSS让网站嗨起来,博客要被玩坏了!
- Linux系统zip压缩命令详细参数,附文件排除选项的正确用法
- 浅谈Android自定义锁屏页的发车姿势
- 移动App入侵与逆向破解技术-iOS篇
- 启用WP Super Cache纯代码版本之后的一些优化措施
- WP Super Cache静态缓存插件纯代码版(兼容多域名网站)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 最大子序列和的问题的解(1)
- 10-STM32+ESP8266+AIR202远程升级方案-功能3-手机APP控制STM32远程更新固件程序,基于ESP8266
- 最大子序列和的接口函数(2)
- 最大子序列和的接口函数(3)
- 【剑指Offer】二叉树的深度
- 运行时间中的对数
- IIC协议
- 通过例子学习编写shell
- 【redis6.0.6】redis源码慢慢学,慢慢看 -- 第三天:MakeFile
- 继续学习Shell脚本(详细)
- 将linux终端的输出信息保存到log中
- UNIX网络编程卷1(第三版)一个简单的时间获取服务器的程序
- Python数据分析实战(3)Python实现数据可视化
- xresloader转表工具链增加了一些新功能(map,oneof支持,输出矩阵,基于模板引擎的加载代码生成等)
- 数据结构之树