好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

一个简易版的T4代码生成"框架"

一个简易版的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:           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  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:   }

作者: Artech
出处: http://artech.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

 

分类:  [14] 框架设计

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于一个简易版的T4代码生成"框架"的详细内容...

  阅读:51次