一个简易版的T4代码生成"框架"
对于企业开发来说,代码生成在某种意义上可以极大地提高开发效率和质量。在众多代码生成方案来说,T4是一个不错的选择,今天花了点时间写了一个简易版本的T4代码生成的“框架”,该框架仅仅是定义了一些基本的基类以及其他与VS集成相关功能的类型而已。[源代码从 这里 下载]
目录
一、T4模版的定义和代码文件的生成
一、T4模版的定义和代码文件的生成
二、TransformationContext与TransformationContextScope
三、Template
四、Generator
五、扩展方法RunCodeGenerator我们先来看看最终的代码生成需要定义那些东西,以及T4模版应该如何定义。对于这个框架来说,代码结构的生成是通过继承自我们自定义基类Template的自定义类型实现的。作为演示,我们定义了如下一个DemoTemplate。从代码可以看出,DemoTemplate仅仅用于生成一个空类,类型名称在构造函数中指定。
1: public class DemoTemplate: Template2: {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 : Generator2: {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 TransformContext2: {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: IDisposable2: {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: TextTransformation2: {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: }四、GeneratorT4文件中最终是通过执行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 Generator2: {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: RemoveUnusedFiles();15: }16: protected virtual void RunCore()17: {18: foreach (var item in this .CreateTemplates())19: {20: item.Value.RenderToFile(item.Key);21: }22: Template template = this .CreateTemplate();23: if ( null != template)24: {25: template.EnsureInitialized();26: template.Render();27: }28:29: }30: protected virtual void RemoveUnusedFiles()31: {32: List< string > codeFiles = new List< string >();33: string directory = Path.GetDirectoryName(TransformContext.Current.Host.TemplateFile);34: foreach (var item in this .CreateTemplates())35: {36: codeFiles.Add(Path.Combine(directory, item.Key));37: }38: var projectItems = TransformContext.Current.TemplateProjectItem.ProjectItems.Cast<ProjectItem>().ToArray();39: foreach (ProjectItem projectItem in projectItems)40: {41: string fileName = projectItem.get_FileNames(0);42: if (!codeFiles.Contains(fileName))43: {44: projectItem.Delete();45: }46: }47: }48: }五、扩展方法RunCodeGenerator
在我们的实例演示中,T4文件中执行Generator是通过调用方法RunCodeGenerator来实现的,这是一个针对TextTransformation的扩展方法。如下面的代码片段所示,方法先根据指定的TextTransformation 和TextTemplatingEngineHost 创建当前TransformContext,对Generator 的Run方法的调用是在当前TransformContext中完成的。
1: public static class TextTransformationExtensions2: {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: }作者: Artech
出处: http://artech.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
分类: [14] 框架设计
作者: Leo_wl
出处: http://www.cnblogs.com/Leo_wl/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权信息查看更多关于一个简易版的T4代码生成"框架"的详细内容...
声明:本文来自网络,不代表【好得很程序员自学网】立场,转载请注明出处:http://www.haodehen.cn/did46818