对于企业开发来说,代码生成在某种意义上可以极大地提高开发效率和质量。在众多代码生成方案来说,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)最终会以如下的方式生成出来。

二、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提供代码生成

一个简易版的T4代码生成框架相关推荐

  1. 依赖注入[5]: 创建一个简易版的DI框架[下篇]

    为了让读者朋友们能够对.NET Core DI框架的实现原理具有一个深刻而认识,我们采用与之类似的设计构架了一个名为Cat的DI框架.在<依赖注入[4]: 创建一个简易版的DI框架[上篇]> ...

  2. 肝一波 ~ 手写一个简易版的Mybatis,带你深入领略它的魅力!

    零.准备工作 <dependencies><dependency><groupId>mysql</groupId><artifactId>m ...

  3. DFiddler:A HTTP Packets Listener一个简易版的手机端的Fiddler。

    Diddler A HTTP Packets Listener一个简易版的手机端的Fiddler. Android系统需要Root权限. PIC_20140121_220503_617.jpeg PI ...

  4. 依赖注入:一个Mini版的依赖注入框架

    前面的章节中,我们从纯理论的角度对依赖注入进行了深入论述,我们接下来会对.NET Core依赖注入框架进行单独介绍.为了让读者朋友能够更好地理解.NET Core依赖注入框架的设计与实现,我们按照类似 ...

  5. 实现一个简易版的微博,包含 client 和 server 两部分,并实现四个基础功能:关注、取关、发微博、获取用户微博列表

    const assert = require('assert'); const question = '实现一个简易版的微博,包含 client 和 server 两部分,并实现四个基础功能:关注.取 ...

  6. javascript实现图片轮播_手撸一个简易版轮播图(上)

    手撸一个简易版轮播图 实现原理,通过控制 swiper-warpper 容器的定位来达到切换图片的效果. 页面布局 简易版轮播图 < > 页面样式 .container{width: 60 ...

  7. 稳扎稳打Silverlight(18) - 2.0视频之详解MediaElement, 开发一个简易版的全功能播放器...

    [索引页] [×××] 稳扎稳打Silverlight(18) - 2.0视频之详解MediaElement, 开发一个简易版的全功能播放器 作者:webabcd 介绍 Silverlight 2.0 ...

  8. 【Linux】用进程控制知识做一个简易版shell

    文章目录 什么是shell 图示 分析 代码 什么是shell shell是命令行解释器的统称 当前使用的shell的名字是bash,bash其实也是一个程序 当前我使用的是centos7下实现一个简 ...

  9. 一个简易版的新闻应用(同时兼容手机和平板)

    代码可能有点长,需要耐心看几遍.前前后后我看了5遍才把整个流程吃透,相信你一定比我聪明!!! 新建一个FragmentBestPractice项目 (让ADT帮我们自动创建活动--活动名:MainAc ...

最新文章

  1. java猜拳游戏代码
  2. BZOJ3632:外太空旅行(最大团,DFS)
  3. 面向对象编程(1)-类和实例
  4. 实名开撕!格力举报奥克斯空调虚标不合格 奥克斯回应:将起诉!
  5. 实现下拉菜单的宽度与登录人ID长度的匹配
  6. CSDN出品,必是精品:CSDN浏览器助手!
  7. ai图像处理软件集大成者:Leawo PhotoIns Pro中文版介绍
  8. bs4.BeautifulSoup获取outerHTML和innerHTML
  9. 2018 UL国际皇冠杯将于10月4日-7日在仁川举行
  10. 使用MediaRecorder录制音频
  11. VMware网络问题排查思路
  12. Java递归求全排列详解
  13. 求生之路2 服务器 修改难度,求生之路2服务器指令及难度参数设置
  14. Sharding-Sphere的新一代Zookeeper注册中心实现剖析
  15. linux pandas教程_十分钟入门 Pandas
  16. HTML-浮动与清除浮动
  17. 折腾 NeoVim 和 SpaceVim
  18. PTA团体程序设计天梯赛-练习集L1-021 重要的话说三遍
  19. 三维数字虚拟GIS沙盘教程第30课:电子地图数据来源分析
  20. Web Spider案例 网洛克 第二题 JJEncode加密 练习(六)

热门文章

  1. 大数据工程师职场面试攻略技巧有哪些?
  2. 基于浏览器的开源“管理+开发”工具,Pivotal MySQL*Web正式上线!
  3. android 点滴积累
  4. AMBA interconnector PL301(一)
  5. 在Mac OS X 10.10.3下使用源码包编译安装GCC5.1
  6. IIS 7中ISAPI筛选器配置
  7. 使用 IntraWeb (26) - 基本控件之 TIWMenu
  8. 04.SpringBoot 自定义配置
  9. 10-4 用select进行调度
  10. springboot租房管理系统答辩PPT模板