T4 文本模板编写准则
如果要在 Visual Studio 中生成程序代码或其他应用程序资源,遵守以下一般准则可能非常有帮助。 它们并不是一成不变的规则。
设计时 T4 模板是在设计时在 Visual Studio 项目中生成代码的模板。 有关更多信息,请参见使用 T4 文本模板生成设计时代码。
- 生成应用程序的可变部分。
-
对于在项目期间可能更改的应用程序部分,或将在不同版本的应用程序之间更改的应用程序部分,代码生成最为有用。 可将这些可变部分与较固定的部分相分离,以便更容易确定必须生成的内容。 例如,如果应用程序提供了一个网站,则可将提供功能的标准页面与逻辑(定义从一个页面到另一个页面的导航路径)相分离。
- 对一个或多个源模型中的变量部分进行编码。
-
模型是一个文件或数据库,每个模板都可以读取该文件或数据库以获取待生成代码可变部分的特定值。 模型可以是数据库、自己设计的 XML 文件、图或域特定语言。 通常,一个模型用于在 Visual Studio 项目中生成许多文件。每个文件都是从单独的模板生成的。
可以在一个项目中使用多个模型。 例如,可以定义一个模型用于在不同网页之间导航,同时定义一个单独的模型用于设置页面布局。
- 应使模型关注用户的需求和词汇,而不是关注您的实现。
-
例如,在网站应用程序中,您将希望模型引用网页和超链接。
理想情况下,应选择适合该模型所表示信息类型的表示形式。 例如,通过网站的导航路径模型可以是一个包含框和箭头的图。
- 测试生成的代码。
-
使用手动或自动测试来验证结果代码是否按用户所需的方式工作。 避免从生成代码的同一模型生成测试。
在某些情况下,可以直接对模型执行常规测试。 例如,可以通过编写测试确保网站中每个页面都可从其他任何页面导航到达。
- 允许自定义代码:生成分部类。
-
除生成的代码外,还允许手动编写的代码。 能够解决可能出现的所有变体情况的代码生成架构并不常见。 因此,添加或覆盖一些生成的代码属正常情况。 生成的材料以 .NET 语言(如 Visual C# 或 Visual Basic)表示时,下面两种策略特别有用:
-
生成的类应是分部类。 这允许您在生成的代码中添加内容。
-
类应成对生成,一个类继承自另一个类。 基类应包含所有生成的方法和属性,派生类只能包含构造函数。 这样便可以使用手写代码覆盖任何生成的方法。
在其他生成的语言(如 XML)中,使用 <#@include#> 指令对手写内容和已生成内容进行简单组合。 在较复杂的情况下,可能必须编写后处理步骤将已生成的文件与任何手写文件组合起来。
-
- 将通用材料移动到包含文件或运行时模板中
-
若要避免在多个模板中重复类似的文本和代码块,请使用 <#@ include #> 指令。 有关更多信息,请参见 T4 包含指令。
还可以在单独的项目中生成运行时文本模板,然后从设计时模板调用它们。 为此,请使用 <#@ assembly #> 指令访问单独项目。 例如,请参见 "Inheritance in Text Templates" in Gareth Jones’ Blog(Gareth Jones 的博客中的“文本模板中的继承”)。
- 考虑将大的代码块移到单独的程序集中。
-
如果您有大的代码块和类功能块,则将此代码中的一些移到您在单独项目中编译的方法中可能会很有用。 可以使用 <#@ assembly #> 指令访问模板中的代码。 有关更多信息,请参见 T4 程序集指令。
可以将方法放置在模板可继承的抽象类中。 抽象类必须从 Microsoft.VisualStudio.TextTemplating.TextTransformation 继承。 有关更多信息,请参见 T4 模板指令。
- 生成代码,而不是配置文件
-
编写可变应用程序的一种方法是编写接受配置文件的通用程序代码。 以这种方式编写的应用程序非常灵活,并且可以在业务需求变化时重新配置,而无需重新生成应用程序。 但是,该方法的一个缺点是应用程序的执行效果不如更具针对性的应用程序。 而且,其程序代码也更难以读取和维护,一部分原因是它必须始终处理最通用的类型。
与此相反,在编译之前生成可变部分的应用程序可以是强类型的。 这样,编写手写代码并将其与软件的已生成部分集成将更容易,也更可靠。
若要发挥代码生成的全部优势,请尝试生成程序代码而不是配置文件。
- 使用生成的代码文件夹
-
将这些模板和生成的文件放入一个名为“生成的代码”的项目文件夹中,明确指出这些是不应直接编辑的文件。 如果创建自定义代码以覆盖或添加到生成的类中,请将这些类放入一个名为“自定义代码”的文件夹中。 一个典型的项目结构如下所示:
MyProject Custom Code Class1.cs Class2.cs Generated Code Class1.tt Class1.cs Class2.tt Class2.cs AnotherClass.cs
- 将通用材料移到继承的模板中
-
可以使用继承在 T4 文本模板之间共享方法和文本块。 有关更多信息,请参见 T4 模板指令。
还可以使用具有运行时模板的包含文件。
- 将大型代码体移到分部类中。
-
每个运行时模板都生成一个与模板名称相同的分部类定义。 可以编写包含同一类的另一个分部定义的代码文件。 还可以此方式将方法、字段和构造函数添加到该类。 可以从模板中的代码块调用这些成员。
这样做的优点是更容易编写代码,因为可以使用 IntelliSense。 另外,您可以在表示形式和基础逻辑之间实现更好的分离。
例如,在 MyReportText.tt 中:
The total is: <#= ComputeTotal() #>
在 MyReportText-Methods.cs 中:
private string ComputeTotal() { ... }
- 允许自定义代码:提供扩展点
-
考虑在 <#+ 类功能块 #> 中生成虚拟方法。 这样单个模板可以用于许多上下文中,而无需修改。 您可以构造提供最少附加逻辑的派生类,而不是修改模板。 派生类可以是常规代码,也可以是运行时模板。
例如,在 MyStandardRunTimeTemplate.tt 中:
This page is copyright <#= CompanyName() #>. <#+ protected virtual string CompanyName() { return ""; } #>
在应用程序的代码中:
class FabrikamTemplate : MyStandardRunTimeTemplate { protected override string CompanyName() { return "Fabrikam"; } } ... string PageToDisplay = new FabrikamTemplate().TextTransform();
- 将数据收集与文本生成分离
-
请尽量避免将计算与文本块混合。 在每个文本模板中,使用第一个 <# 代码块 #> 来设置变量并执行复杂的计算。 从第一个文本块往下到模板的末尾或第一个 <#+ 类功能块 #>,请避免长表达式,还要避免循环和条件(除非它们包含文本块)。 这种做法可使模板更容易读取和维护。
- 不要为包含文件使用 .tt
-
使用不同文件扩展名,例如对于包含文件,使用 .ttinclude。 仅对要作为运行时或设计时文本模板处理的文件使用 .tt。 在某些情况下,Visual Studio 将识别 .tt 文件并自动设置它们的属性以便处理。
- 将每个模板作为固定原型启动。
-
编写要生成的代码或文本的示例,并确保其正确。 然后将其扩展名更改为 .tt,并增量插入通过读取模型修改内容的代码。
- 考虑使用类型模型。
-
虽然可以为模型创建 XML 或数据库架构,但创建域特定语言 (DSL) 可能会很有用。 DSL 具有以下优点:生成类来表示架构中的每个节点以及生成属性来表示特性。 这意味着可以根据业务模型编程。 例如:
Team Members: <# foreach (Person p in team.Members) { #> <#= p.Name #> <# } #>
- 考虑为您的模型使用关系图。
-
许多模型都是以文本表的形式来最有效地显示和管理,特别是它们非常大时。
然而,对于某些类型的业务需求,阐明复杂的关系和工作流集非常重要,而关系图是最适合的媒体。 关系图的优点是方便与用户和其他利益干系人一起讨论。 通过在业务需求级别从模型生成代码,可使您的代码在需求变化时更加灵活。
UML 类和活动图可以经常为这些目的而进行调整。 您还可以将自己的关系图类型设计为域特定语言 (DSL)。 可以从 UML 和 DSL 生成代码。 有关更多信息,请参见 建立应用程序模型 和 建立应用程序模型。
Blogger Labels: 文本模板编写准则