T4 模板

减少使用 T4 合并代码生成时遇到的问题

Peter Vogel

 

微软。NET 框架使代码生成 (当将控件拖到设计图面上生成的代码) 的设计时和运行时 (当 LINQ 生成 SQL 语句的检索数据) 都广泛使用。 代码生成显然使开发人员更高效通过减少开发人员必须编写,代码的数量,但很多解决方案中使用相同或更少的代码时,它可以特别有用。 随着您在新的应用程序中实现此类似 (但不是完全相同) 的代码,是太容易引发新的错误。

虽然开发人员可以利用的所有代码生成工具。NET Framework 中创建的应用程序使用,很少在他们日常的发展实践中使广泛使用代码生成。 有许多的原因:他们担心纳入代码生成,需要一个新的工具集,他们没有时间去学习 ; 他们缺乏经验,在代码生成将会解决的问题的认识和设计将生成的代码与"手写"代码 ; 集成的解决方案 他们明白一些代码生成解决方案可能需要更多的时间,以维持 (或甚至要使用),并不是通过应用解决方案将被保存。

Microsoft 文本模板转换工具包 (T4) 能解决许多问题,这些问题提供了简单的方法,执行代码生成解决方案,利用工具和技术开发人员已经与舒适。 T4 的做法是认识到典型的解决方案涉及两种类型的代码:到另一个,不会更改从一个代码生成的样板代码并不会更改的动态代码。 T4 允许开发者只需键入解决方案的样板代码部分到文件中,从而简化了代码生成。 在 T4、 动态代码 (通常的一小部分代码生成解决方案) 生成使用一组看起来非常类似于一个 ASP 标记的标记。NET MVC 开发人员将会创建一个视图,ASP。网的开发者会使用.aspx 文件中嵌入服务器端代码。

您已经有通过让您指定的输入生成代码,无论您已经在使用的编程语言中使用的基于 T4 的解决方案利用了技能。 T4 不生成非特定代码的解决方案 (T4 解决方案生成的代码是总是在一些特定的编程语言),但是大多数开发人员不需要非特定代码的解决方案。

定义代码生成解决方案

虽然 T4 很容易地创建代码生成解决方案,开发人员必须在承认时代码生成的解决方案是有用和在实际设计解决方案的问题没有解决。 代码生成一般可以解决的问题分享三个特点:

  1. 生成的代码从发展将进入一个单独的文件­oper 的代码。 这可确保代码生成过程不会干扰在开发人员的代码。 通常,这意味着从他的"手写"代码生成的代码将进入一个新的类,开发人员将使用。 可能是有意义生成一个分部类,开发人员可以调用不仅扩展 — — 但在开发人员的代码和生成的代码仍保留在单独的文件。
  2. 生成的代码是重复:在解决方案中的代码是一个模板,可以重复多次,往往较小的变化。 这确保代码生成更简单、 更易维护比手写代码等效。
  3. 生成的解决方案手写的解决方案相比,需要更少的投入 (理想情况下,没有在所有的投入 — — 代码生成解决方案确定什么需要从环境做)。 如果输入的数目很大或难以确定,开发人员可能会把作为生成解决方案比简单的手写的解决方案。

由于这些特点,三种类型的方案值得调查用于代码生成。 开发者倾向于关注几个输入用于生成许多常用的代码的第一个方案 ("最终方案") (实体框架,例如认为)。 其实,最富有成效的代码-­代机会分为两个其他方案。

第二种情况是一个最明显的:时有很大的代码需要生成。 在这种情况下,避免编写多个重复行是代码的显然是代码的有益的。 虽然大多数代码生成解决方案在多个应用程序中使用,在此类别中的解决方案是值得的即使在只有单个应用程序中使用。 而不是广义的写的代码,很多如果语句来处理每种情况 — — 每个 If 语句代码的逻辑的复杂性增加一倍 — — 代码生成解决方案可以生成特定的代码所需的每个组的条件。 手编码许多共享接口的类,而不是代码生成可用于创建各个类 (假设类共享共同的结构)。

但方案中的第三种是最常见的:当几个投入将生成一点点的许多应用程序中使用的代码。 在此方案中,在任何特定的应用程序中的重复代码的数量很少但代码支持的活动是非常常见的解决方案最终产生了大量的代码 — — 只是不在任何一个应用程序中。

例如,下面的是一些典型的 ADO 代码。净开发人员编写的所有时间:

string conString =
  System.Configuration.ConfigurationManager.
ConnectionStrings["Northwind"].ConnectionString;

虽然这是微不足道的代码量,这是重复的代码 — — 只是名称的连接字符串更改 — — 在应用程序中后应用。 此外,在没有任何智能感知支持的连接字符串名称,代码是出错:它是开放的会仅在运行时被发现的"拼写检查计数"错误 (可能时有输入到您评价的人看在你肩上)。 另一个例子实施 INotifyPropertyChanged,使得开发人员打开"拼写检查计数"错误每个属性。

此代码检索连接字符串命名为罗斯文会更有用,如果创建的每个连接字符串,像这样的 ConnectionManager 类存在的一些代码生成解决方案:

string conString = ConnectionManager.Northwind;

一旦您识别代码生成解决方案的机会下, 一步就是代码的写出该解决方案将生成的示例。 在此情况下,ConnectionManager 类可能像下面这样:

public partial class ConnectManager
{
  public static string Northwind
    {
      get
        {
          return System.Configuration.ConfigurationManager.
ConnectionStrings["Northwind"].ConnectionString;
        }
    }
}

此代码满足标准的代码生成解决方案:它是重复 (为每个连接字符串的属性代码重复) 只有小变化 (连接字符串的名称) 和属性名称和投入的多少是小 (只是连接字符串的名称)。

您的第一个代码生成模板

T4 解决方案可以包含一个"代码生成包":开发人员位置插入的代码生成程序和模板文件,从这些输入生成代码的输入文件。 这两个文件是 T4 模板文件,并创建使用相同的编程工具来编写应用程序使用。 这种设计可以单独提供进程投入的开发人员使用的文件从您的代码生成模板。

要开始创建您的代码生成解决方案,您必须添加 T4 模板文件将生成您应用程序的代码,可以对其进行测试 — — 最好是,应用程序相似您期望您的解决方案中使用。 在添加新项对话框中所示的 Visual Studio 中添加 T4 模板文件, 图 1,添加文本模板指定适合于您的代码生成模板 (例如,ConnectionManagerGenerator) 的名称。 如果您的版本的 Visual Studio 没有文本模板选项,添加触发器 T4 处理的一个新文本文件 (还在常规部分中找到),给该文件的扩展名为".tt"。 如果您添加文本的文件,您将获得一条警告消息,您可以安全地忽略。

Adding a T4 Template
图 1 添加 T4 模板

如果您检查属性为新的模板文件,您就会发现其属性已设置为 TextTemplatingFileGenerator 的自定义工具。 此自定义工具由 Visual Studio 会自动运行和管理代码生成过程的主机。 T4、 模板文件的内容被通过在代码生成的主机,将导致生成的代码放入模板文件嵌套的子文件。

如果文本的文件添加到您的项目后,您的模板文件为空 ; 如果您能够添加模板文件,它将包含两个 T4 指令,标有 < # @ … … # > 分隔符 (如果您添加文本的文件,则需要将这些指令添加)。 这些指令指定的语言,该模板将写在 (不为生成的代码的语言) 和子文件的扩展名。 在此示例中,两个指令设置模板的编程语言 Visual Basic 和子文件包含要生成的代码的文件扩展名为。 generated.cs:

<#@ template language="VB" #>
<#@ output extension=".generated.cs" #>

若要创建传统的世界你好"应用程序,只是到模板文件 (注意 Visual Basic 中正在写入该模板的语言时,该模板生成的 C# 代码) 添加代码:

public class HelloWorld
{
  public static string HelloWorld(string value)
  {
    return "Hello, " + value;
  }
}

此示例使用仅样板代码。 在 T4、 样板代码直接从模板复制到代码文件。 在 Visual Studio 2010 年应该发生当您切换模板文件,或将其保存。 由模板文件在解决方案资源管理器中右击并从其上下文菜单中选择运行自定义工具或单击解决方案资源管理器顶部的变换的所有模板按钮,也可以触发代码生成。

后触发一代,如果您打开的模板代码文件中 (这将具有该模板的输出指令中指定的扩展),你会发现它包含您的模板中指定的代码。 Visual Studio 将还做没有背景编译的新代码中,这样你就会发现,您可以使用生成的代码从您的应用程序的其余部分。

生成代码

然而样板代码是不够的。 ConnectionManager 解决方案必须动态生成应用程序需要的每个连接字符串的属性。 要生成此代码,必须添加管理代码生成过程和某些变量将包含使用您的代码生成解决方案的开发人员从投入控制代码。

ConnectionManager System.Collections 命名空间中使用数组列表 (这是我打过电话的连接) 保存表单代码生成过程的输入的连接字符串的列表。 要导入该命名空间的使用,由您的模板中的代码,您可以使用的 T4 导入指令:

<#@ Import Namespace="System.Collections" #>

现在,您可以添加任何开始您生成的类的静态代码。 因为我生成的 C# 代码,ConnectionManager 的初始代码如下所示:

public partial class ConnectionManager
{

我现在必须添加控件代码将动态地生成输出代码。 控制 (代码是执行,而不复制到子文件) 生成的代码必须括在 < … … # # > 分隔符。 在此示例中,为便于区分控制代码和代码生成的我已经写在 Visual Basic (这不是一项要求的代码生成过程) 的控制代码。 ConnectionManager 解决方案的控制代码的每个连接字符串连接集合中循环:

<#
  For Each conName As String in Connections
#>

在您的模板中的任何控制代码,还需要包括其值均应纳入动态生成代码的任何表达式。 在 ConnectionManager 的解决方案中,连接字符串的名称已被纳入到属性的声明和传递给 ConnectionStrings 集合的参数。 要计算的表达式,并将其插入到样板代码的值,该表达式必须括在 < # … … = # > 分隔符。 此示例动态插入值从 conName 变量内对于静态代码中的两个地方每个循环:

public static string <#= conName #>
{
  get
  {
    return System.Configuration.ConfigurationManager.
ConnectionStrings["<#= conName #>"].ConnectionString;
  }
}
<#
  Next
#>
}

就只剩是连接的定义数组列表包含列表中字符串的名称。 为此,我将使用一个 T4 类功能。 T4 类特征通常用于定义 helper 函数,但也可以用于定义字段或将代码生成过程中使用的任何其他类级项目。 这一个不会类特征必须出现在一个模板,结束时:

<#+
  Dim Connections As New ArrayList()
#>

此 T4 模板 ConnectionManager 解决方案的第一部分 — — 代码生成模板。 现在,您需要创建解决方案的第二部分:开发人员会使用提供代码生成过程的初始的输入的文件。

使用代码生成软件包

若要是提供一个位置,输入代码生成过程的初始开发人员,您添加第二个 T4 模板到应用程序中,您要测试您的代码生成解决方案。 此模板必须具有一个包含指令,将您的代码生成模板复制到此模板。 因为我命名我的代码生成模板文件 ConnectionManagerGenerator,ConnectionManager 解决方案的输入的模板文件如下所示:

<#@ template language="VB" #>
<#@ output extension=".generated.cs" #>
<#@ Import Namespace="System.Collections" #>
<#
#>
<#@ Include file="ConnectionManagerGenerator.tt" #>

当执行代码生成时,主机进程实际上装配中介。净程序指令、 控制代码和静态代码从您的模板中指定,然后再执行,由此产生的程序。 它是从该中介程序注入到模板中的子文件输出。 使用包含指令的结果是合并您的代码生成模板 (带其声明的连接 ArrayList) 使用此文件,以创建该中介程序的内容。 使用您的解决方案的开发人员必须要做的就是将代码添加到此模板将设置您的代码生成模板所使用的变量。 此过程使开发人员能够指定要使用的编程语言,它们要使用的代码生成的输入。

对于 ConnectionManager 的解决方案,开发人员需要添加的应用程序的 app.config 或 web.config 文件,以连接数组列表中列出的连接字符串的名称。 因为这些设置都需要执行的控制代码的一部分,该代码必须包含在 < … … # # > 分隔符。 开发者的代码也必须在前面包含指令,以使您的代码执行之前设置的变量。

要生成 ConnectionManager 的称为罗斯文和 PHVIS 的两个连接字符串,开发人员将此代码添加包含指令之前输入的模板:

<#
  Me.connections.Add("Northwind")
  Me.connections.Add("PHVIS")
#>
<#@ Include file="ConnectionManagerGenerator.tt" #>

您现在可以由代码生成模板文件和输入的模板文件组成的代码生成软件包。 使用您的解决方案的开发人员必须将这两个文件复制到其应用程序中,在输入文件中,设置变量和关闭或保存该输入的文件生成解决方案的代码。 进取生成代码的开发人员可以设置代码生成包作为 Visual Studio 项目模板,其中包含两个模板文件。 不适当到 ConnectionManager 的解决方案,如果开发人员需要生成基于不同的输入的代码的另一个组,而他只被需要使容纳投入的第二个集的输入文件的第二个副本。

此解决方案的结构有一皱:使用此解决方案的任何应用程序必须输入的模板和代码生成模板。 在 ConnectionManager 的解决方案中,如果两个模板生成代码,Visual Studio 将进行编译,两个生成的代码文件将会都定义了一个类名连接管理器和应用程序不会编译。 有多种方法防止这一点,但最简单的方法是修改您的代码生成模板,使其生成的代码文件的扩展名,Visual Studio 不会承认。 更改代码生成模板文件中的输出指令没有诀窍:

<#@ output extension=".ttinclude" #>

代码生成工具和资源

MSDN 库页中,除了使用 T4 的信息的最佳来源是在奥列格 Sych 博客 olegsych.com— — 我当然也取决于他的见解 (和工具) 开发自己的代码生成解决方案。 他 T4 工具箱中包含几个模板用于开发 T4 解决方案 (包括模板,用于生成多个输出文件从一个单一的 T4 模板和其他代码生成过程管理的工具)。 Sych 的工具包还包括几个代码生成方案的软件包。

Visual Studio 本质上是将视为文本的文件的 T4 模板 — — 这意味着你没有智能感知支持或突出显示或,真的,是任何的开发人员希望的东西从编辑器。 在 Visual Studio 扩展管理器中,您会发现将会提高你的 T4 发展的几个工具的经验。 这两个视觉 T4 从 Clarius 咨询 (bit.ly/maZFLm) 和 T4 编辑器从 Devart (bit.ly/wEVEVa) 会给你的许多你想当然的编辑器中的功能。 或者,您可以从有形工程获得 T4 编辑器 (要么免费或 PRO 版) (请参阅图 2) 在 bit.ly/16jvGY,其中包括可视设计器,您可以使用创建代码生成软件包从统一建模语言 (UML)-像图。

The Default Editor for T4 Template Files (Left) Isn’t Much Better than NotePad—Adding the Tangible Editor (Right) Gives You the Kind of Features You Expect in Visual Studio
图 2 (左) T4 模板文件的默认编辑器并不比记事本 — — 添加有形的编辑器 (右) 为您提供您在 Visual Studio 中期望的特征种

正如其他任何基于代码的解决方案,它是不太可能您的解决方案将工作第一次。 编译错误模板控件中的代码在错误列表中后报告从一个模板文件上下文菜单中选择运行自定义工具。 但是,即使您的控制代码编译,您可能会发现该模板的子文件是空,只有 ErrorGeneratingOutput 这个词。 这表明它执行时,生成的控制代码,您的代码生成软件包中的错误。 除非您的错误是明显的你就需要调试该控件的代码。

若要调试您的生成软件包,必须首先设置调试属性为 True,像这样的模板指令:

<#@ template language="VB" debug="True"#>

现在可以在控件的代码中设置一个断点,并有尊重它的 Visual Studio。 然后,调试您的应用程序的最可靠方法是启动第二个版本的 Visual Studio,并从调试菜单中,选择附加到进程。 在出现的对话框中选择的 devenv.exe 正在运行另一个副本并单击连接按钮。 你现在可以返回您的 Visual Studio 的原始副本,并从您输入的文件的上下文菜单中使用运行自定义工具,来开始执行您的代码。

如果该进程不工作,可以触发调试通过这条线插入模板中的控件的代码:

System.Diagnostics.Debugger.Break()

与 Visual Studio 2010 在 Windows 7 上,应在你休息方法调用之前添加这行:

System.Diagnostics.Debugger.Launch()

当您的模板代码执行时,这条线将打开一个对话框,您可以重新启动 Visual Studio 或调试它。 选择调试选项将开始第二个副本的 Visual Studio 已经连接到您的模板代码运行的进程。 调试代码时,您的 Visual Studio 的初始实例将被禁用。 不幸的是,当您调试会话结束时,Visual Studio 将留在该禁用模式。 要防止出现这种情况,您需要更改其中的 Visual Studio 在 Windows 注册表中设置 (请参见调试在 T4 的 Sych 的邮政 bit.ly/aXJwPx 的详细信息)。 你要记住要删除此语句,一旦修复了您的问题。

当然,此解决方案仍然对开发人员进入输入文件正确的连接字符串的名称计数。 更好的解决方案将会有包括应用程序的配置文件,无需输入任何投入的开发人员从读取连接字符串的代码 ConnectionManager。 不幸的是,因为在设计时 (而不是在运行时生成的代码,您不能使用配置应用程序读取配置文件,并将需要使用 System.XML 类处理配置文件。 您还需要添加程序集指令,捡这些类,如我较早获得的 System.Collections ArrayList。 通过将程序集指令的名称属性设置为您的 DLL 的完整路径,还可以添加您自己的自定义库的引用:

<#@ assembly name="C:\PHVIS\GenerationUtilities.dll" #>

这些都是您的工具包,添加代码生成和提高您的生产力要素 — — 连同您的代码的质量和可靠性。 T4 轻松开始让你创建使用熟悉的工具集的代码生成解决方案。 你将使用 T4 中面临的最大问题学习与识别应用这些工具的机会。

Peter Vogel  是在 ph 值及 V 信息服务的主体。 他最后一本书是"在实际代码的生成。NET"(艾迪生 - 韦斯利专业,2010年)。 Ph 值和 V 信息服务专业在便利的基于服务的体系结构设计和集成。到这些体系结构的网络技术。 在他的咨询实践,Vogel 写了学习树国际 SOA 设计课程,世界范围内教。

多亏了以下的技术专家审查这篇文章:Gareth Jones