资讯动态

T4模板完全入门:Visual Studio中自动化代码生成的实战指南

发布时间:2026/10/17 9:42:08 来源:尧图企业网站定制
1. 内容整体设计与思路拆解1.1 什么是T4模板为什么它值得你花一个小时搞明白T4模板全称 Text Template Transformation Toolkit是微软在 Visual Studio 中内置的一套文本模板生成引擎。简单说它允许你通过一个混合了静态文本和 C# 代码的.tt文件在编译期或设计期自动生成任意文本内容——最常见的用途当然是生成代码。我第一次接触它的时候第一反应是“这不就是写个控制台程序再输出字符串吗”但用顺手之后才发现这两者之间的体验差异相当于手写一篇文章和用一套排版系统写一篇文章的差别。你不妨这样理解 T4 的定位把“重复代码”的生产过程变成一条流水线。比如项目里有几十个结构相似的数据实体类手写的话只是复制粘贴改字段名费时间不说还容易漏改。用 T4 模板你把公共结构写一遍再用循环把差异部分自动填进去一键生成干净利落。这篇文章适合谁主要面向 .NET 平台的中初级开发者或者是那些“听说过 T4 但一直没找着门”的朋友。读完这篇你至少能做到能看懂一个.tt文件的大致含义能自己创建模板并运行能解决“生成不了”“生成出来是乱的”这类最劝退的问题。至于高级的 T4 预处理工具、设计时程序集引用、多文件生成这些放到系列后面的文章里去讲。1.2 整套方案的实现思路和前后端支撑为什么用 T4 而不是直接用代码生成器比如自己写个 Console 程序来生成代码这背后其实是个“工程便利性”的问题。写控制台程序你得考虑项目建在哪里、如何传递参数、生成结果写回到哪个目录、要不要加入源码版本控制。而 T4 天然活在 Visual Studio 里你保存一下.tt文件输出文件立刻出现在工程目录中并且自动嵌套在模板文件下层参与编译。这种“零仪式感”的体验是独立代码生成器很难给的。我见过某团队直接写了个命令行工具来生成业务代码工具挺完善参数也齐全但用起来要开个终端、敲长串命令消息映射错了还要先看文档。后来换成 T4 模板普通开发把 Entity 的字段列表贴进去点一下“运行自定义工具”代码就出来了。这个转变给我留下印象特别深——工具链嵌在 IDE 里永远比外置工具用起来顺手。再从技术支撑的角度看T4 模板天生能引用程序集、遍历项目中的类型、读取配置文件甚至能连接到数据库读 Schema。这意味着它的输入源非常灵活可以来自“硬编码在模板里的一个字符串数组”也可以来自“一个正在编译的项目”还可以来自“线上数据库的表结构”。这正是它比很多“固定骨架”的生成器强大的核心点——模板只是壳里面的逻辑可以是真正的 C# 程序。1.3 方案选型模板学习先从最简单的“静态模板”切入T4 模板的学习曲线其实是很友好的但官网文档和某些教程上来就扔出一个集成了数据库访问、反射遍历、自定义指令处理器的完整模板直接把新手吓跑了。我的建议是第一课永远从最简单的静态模板开始。你不需要理解什么CodeDomProvider也不用管ITextTemplatingEngineHost只用认识模板的三大组成——指令块、文本块、代码块再加一个# #表达式标签就能开始干活。等这些基础概念通了再逐步引入循环、条件判断、外部参数传入、反射遍历类型。这样循序渐进每走一步都有看得见的输出学起来非常有成就感。本文第一部分先讲透“认识模板 简单使用”我挑了一个实际项目中一定会碰到的场景——根据配置信息批量生成数据访问实体——但又刻意把场景简化成“只有几个字段”的程度方便你把注意力放在模板本身而不是业务复杂度上。我们还要想清楚一个边界T4 模板是设计时代码生成工具它本身不参与运行时逻辑。它产出的文件可以是.cs文件也可以是.txt、.xml、.html甚至可以是.sql。所以模板写完运行一次后面你就把它当“存档”放着以后需求变了再改模板再运行。整个思路核心就一句话用代码写代码把生成规则固定下来把变化的部分作为输入暴露出来。2. 核心细节解析与实操要点2.1 T4模板工程的目录结构与搭建T4 模板不是一个“需要独立安装”的东西凡是装好了 Visual Studio 且安装了 .NET 桌面开发工作负载的机器都自带 T4 环境。你要做的只是把一个后缀为.tt的文件加入项目。实际操作上有两种方式在解决方案资源管理器里右键项目 - 添加 - 新建项 - 搜索“文本模板”选择“文本模板”或“已预处理文本模板”。直接添加一个“文本文件”然后把后缀手动改为.ttVisual Studio 会自动识别并改变图标。我建议第一次练习用第二种方式因为你还能顺便看看加进项目前后图标的变化。加进来之后文件默认是空的第一行通常是# template languageC# #那是不是不写这一行就不能用了实测下来不能。你新建的文本文件默认没有语言声明直接写纯文本是可以的但一旦里面出现 C# 代码块就必须声明languageC#。所以我习惯上的做法是一上来就把这行写上省得后面忘了。还有一点容易栽坑.tt文件的编码最好保存为“带 BOM 的 UTF-8”。如果不带 BOM某些中文字符串在生成结果里可能变成乱码。具体操作是在 Visual Studio 里用“文件 - 另存为”点保存按钮旁边的下拉箭头选“编码保存”再选 UTF-8 with signature。这个细节我在实践中遇到太多次了。2.2 模板构成三要素文本、代码、表达式把一个.tt文件打开看你会发现里面混合了好几种“长相”完全不同的内容。它们分别是文本块不需要任何包裹符号直接写的普通文字。输出时被原样原封不动地保留。代码块用# ... #包裹起来的 C# 代码。这段代码是用来控制流程的执行后不会直接输出到结果文件。表达式块用# ... #包裹起来的 C# 表达式。会计算出一个值然后把这个值输出到结果文件中。文字形容可能比较抽象我们直接看例子。比如模板内容是这样你好# 世界 #! # var list new[] { A, B, C }; foreach (var item in list) { # 输出项# item # # } #生成结果是什么我来一行行拆解。第一行“你好”是文本块原样输出# 世界 #表达式输出一个字符串“世界”于是第一行得到“你好世界!”。接着代码块里定义了一个数组 A、B、C然后进入 foreach 循环——注意循环体开始和结束之间有一行“输出项”文本这一行会在每次循环时输出一次后面跟着的# item #会把当前项的值拼上去。最终结果你好世界! 输出项A 输出项B 输出项C这个例子虽然简单但已经覆盖了 T4 的三个核心语法要素的全部。你要是能把上面这个例子的输出结果自己推演一遍T4 的“分段输出”机制就算入门了。所谓分段输出是 T4 引擎执行时的核心机制文本块默认随手就写进输出流代码块则只是在执行而表达式块产生的内容会立即写入输出流。理解了这一点后面写复杂模板时才不会晕。2.3 在Visual Studio里运行模板并检查输出写好的模板不会自动运行你得让它跑起来。最常用的方式是右键单击.tt文件选择“运行自定义工具”。注意这个菜单项不是默认显示在右键菜单顶层的有时你需要在“打开方式”附近找或者先让我说一句如果找不到看一眼文件属性里的“自定义工具”是不是空的。正常情况下新建的模板文件其自定义工具被设置为TextTemplatingFileGenerator右键菜单就会出现这个命令。还有一种更“懒”的方式保存文件。啊对没有开玩笑.tt文件一旦被保存或剪贴板中失去焦点时触发保存行为Visual Studio 就会自动运行那个自定义工具。这意味着你把模板改完CtrlS 一下输出文件立刻更新。这个特性在后续调优时会非常有用但第一次用的时候也有不少人被坑到——你只是保存了一下还没别的操作生成的.cs文件就变了还要注意别把没改完的半成品提交到 Git 里。输出文件长什么样如果模板文件名是Demo.tt那么生成输出的默认文件名是Demo.txt如果你没显式指定输出文件后缀的话。要自定义输出文件名和扩展名可以在模板里加一行# output extension.cs #这一行会告诉引擎把生成的内容写到一个以.cs结尾的文件里文件名默认还是和模板同名。我把这行指令当作模板的标准配置几乎每个真实项目模板都会写它因为它直接决定了生成结果的类型——做代码生成输出.cs最自然做配置文件生成写.config想快速生成一段 HTML 报告写.html。2.4 设计时调试与断点技巧很多人以为.tt文件没法调试觉得运行期出了错只能靠猜。这其实是误解。Visual Studio 支持对 T4 模板进行设计时调试操作方式如下在.tt文件的代码块里找到你想断住的某一行 C# 代码。在该行左侧边距处打一个断点。右键.tt文件 - “调试 T4 模板”。这时候引擎会挂起调试器执行到你打的断点时停下来你可以用即时窗口检查变量的值也可以单步执行。这个方法在排查“循环没进到”“条件判断不对”等问题时极其高效。我至今记得第一次用这个功能时的感觉——原来模板调试跟普通 C# 调试一样顺畅之前的困惑有点冤。不过有个前提要提醒直接右键菜单里的“运行自定义工具”模式下是没法断点调试的必须先通过“调试 T4 模板”入口启动。这一个细节很容易被忽略导致你打了断点却完全没反应。2.5 用错误列表和输出窗口定位生成失败模板运行失败时Visual Studio 会在“错误列表”里给出错误的描述和大致行号。很多人看到“文本模板”报错就直接懵了实际上它的错误信息还是比较诚实的。常见的报错有Compiling transformation: ...模板代码里的 C# 片段有语法错误。比如单词拼错、括号不配对。Cannot convert type string to int你用在表达式块里的某个变量类型不对。某个程序集引用找不到模板里引用了某个 DLL 里的类型但引擎没找到那个程序集。拿到错误信息后我的排查顺序是先看行号再去看该行对应的内容是文本块还是代码块。如果错误行指向的是一段被# #包起来的代码那语法错误的可能性最大。如果错误行指向的是文本块那通常是编码问题比如中文字符串乱码导致引擎解析异常或者模板中出现了孤立的#但没配对。还有一点如果你在模板里输出了一段 C# 代码即生成结果是.cs文件而生成的代码在编译时报错那属于另一类问题。排查时要分清是模板语法错还是生成后的代码错。我见过不少新手把这两类问题混在一起结果在模板里翻半天实际上问题出在输出内容里。最简单的区分方法看错误列表里报错的文件名是.tt还是.cs。如果是后者就去修模板里生成逻辑、而不是改已生成的代码——因为下次运行模板你改的东西又没了。3. 实操过程与核心环节实现3.1 案例需求生成三行固定报文理论讲多了容易飘我们直接进入一个完整的实操例子。假设你正准备做一个对接第三方支付渠道的小工具对方要求每个请求都以一段固定格式的报文开头。这个报文里包含版本号、商户号、数据格式和回调地址。这种需求在项目中特别常见完全可以手工复制但如果后续要支持几十个商户渠道你就知道“写死”有多痛苦了。我们用 T4 优化掉这部分重复工作。最终期望生成的.txt文件内容如下VERSION1.0 MERCHANT_ID100023 FORMATJSON CALLBACK_URLhttps://api.example.com/callback我把版本号、商户号、格式这些放进去先不引入列表和条件判断保持最简单的“输出文本”效果。你先把这个过程跑通后面再玩出花来。整个过程的本质是模板就是一个“参数化的字符串”你把固定的文本原样写好把可变的参数替换成表达式。工程上把这个步骤叫“模板实例化”——写模板是在定义“类”运行模板是在“new 对象”生成的文本就是对象实例。3.2 一步一步创建模板文件第一步在任意一个类库项目或者随便建一个空项目里右键 - 添加 - 新建项 - 文本模板命名为PayMessage.tt。如果你没找到“文本模板”入口去“已安装 - Visual C# - 常规”类别下找有的版本把它放在了“常规”里。它的图标是一个偏蓝绿色的小齿轮不认识也没关系后缀.tt是唯一判断标准。第二步把模板内容替换成下面这段。我先贴出来再逐行讲。# template languageC# # # output extension.txt # # var version 1.0; var merchantId 100023; var format JSON; var callbackUrl https://api.example.com/callback; # VERSION# version # MERCHANT_ID# merchantId # FORMAT# format # CALLBACK_URL# callbackUrl #指令块里两行分别声明了语言和输出后缀。代码块里定义了四个变量注意这四个变量是 C# 代码它们不会直接被输出——这可能会让第一次接触的人觉得奇怪“那我定义了它们到底输出什么呢”关键就在于后面那四行文本里的表达式块# version #。引擎执行到这一行时会把当前作用域里version变量的值取出来写进输出流。这四行文本块和表达式块交错在一起的效果正是 T4 的核心工作方式那种在 HTML 模板里嵌入% %的感觉跟你之前在 ASP.NET WebForms 或 Razor 里写过的差不多。如果你会 Razor学 T4 几乎是零成本迁移。第三步保存文件。在保存的一瞬间Visual Studio 自动触发“运行自定义工具”你应该能在解决方案资源管理器中看到PayMessage.txt出现在PayMessage.tt下面。双击打开查看内容是不是和目标报文一致。3.3 参数化管理把常量提为配置项刚才的例子有个小瑕疵变量直接写在模板里虽然比“散落在四个文件里”强了不少但改版本号时还是得打开模板改代码。这不是不行只是不够“配置化”。项目里更常用的做法是把可变参数放到一个单独的地方模板再从那里读取。常见的做法有三种用app.config或web.config的AppSettings。用单独的 XML/JSON 配置文件。用 T4 的parameter指令传入外部参数。其中最简单、适合新手入门的是第一种。在项目中新建一个App.config往里面塞几行设置?xml version1.0 encodingutf-8 ? configuration appSettings add keyVersion value1.0 / add keyMerchantId value100023 / add keyFormat valueJSON / add keyCallbackUrl valuehttps://api.example.com/callback / /appSettings /configuration然后在模板里读它。T4 模板运行时本质上是一个托管程序它默认加载了当前目录和 GAC 里的 dll但要读App.config需要先引一下System.Configuration程序集。下面是升级版模板# template languageC# # # output extension.txt # # assembly nameSystem.Configuration # # import namespaceSystem.Configuration # # var version ConfigurationManager.AppSettings[Version]; var merchantId ConfigurationManager.AppSettings[MerchantId]; var format ConfigurationManager.AppSettings[Format]; var callbackUrl ConfigurationManager.AppSettings[CallbackUrl]; # VERSION# version # MERCHANT_ID# merchantId # FORMAT# format # CALLBACK_URL# callbackUrl #注意两个新指令# assembly name... #告诉引擎我需要引用哪个程序集。凡是模板代码里用到的类型所在的程序集都最好显式声明。不然会出现代码写得没错编译模板时却报告“找不到类型”的尴尬。# import namespace... #相当于 C# 代码文件里的using语句把命名空间引入之后写类型名就可以省略前缀。这里建议养成一个习惯模板里用到的一切外部类型都通过assembly和import显式声明不要依赖 GAC 里碰巧存在的程序集。否则模板在不同的开发机上可能会出现“你本地能生成别人机器上就报错”的诡异问题。实测一下运行这个模板生成的结果和之前一模一样。但你现在再改商户号只需打开App.config改一行不用去碰.tt文件。对于团队协作来说这种分离特别重要——运营不小心把新商户号发到群里开发只需要把它同步到配置里模板根本不用动。3.4 批量输出用循环结构生成多条记录需求随之升级报文不止给一个商户用而是要给好几个。这时如果还在模板里逐行写死表达式那就太蠢了。我们应该把可变数据做成一个数组或ListT然后用循环输出。模板代码改成这样# template languageC# # # output extension.txt # # var configs new[] { new { Version 1.0, MerchantId 100023, Format JSON }, new { Version 1.0, MerchantId 100024, Format XML }, new { Version 1.1, MerchantId 100025, Format JSON }, }; # # foreach (var config in configs) { # MERCHANT_ID# config.MerchantId # VERSION# config.Version # FORMAT# config.Format # # } #这个例子教你怎么在“文本块”和“代码块”之间来回切换去实现循环输出。具体来说foreach的代码块不写任何文字进入循环体之后后面紧跟的文本块# config.MerchantId #内容会在每次循环时都执行一次。循环体结束之前有一个空行文本块它会使每条记录之间产生一个空行作为视觉分隔。这里有一个非常容易踩坑的点文本块中的每一行包括空行都会被原样输出。在 T4 中\r\n换行符也会被视为文本内容输出去。所以当你准备用循环结构时空行的位置、数量会影响最终结果的排版。如果你希望所有输出之间没有任何空行就不要在循环体末尾留空白行或者用代码块把空白行吃掉。我用“吃掉”这个词是因为在 T4 的世界里针对多余空行有一条经典的处理手段——给#和#加修饰符#表示类特性控制块用于定义辅助方法这个先不提。#是指令块。#前如果加上一个空格或者任何字符会直接影响输出结果中对换行的保留。更常用的处理多余空行的方法是在代码块里输出\n或\r\n让换行由你的代码来控制而不是由模板里的换行符控制。比如# foreach (var config in configs) { ## config.MerchantId #|# config.Version #|# config.Format ## if (!ReferenceEquals(config, configs[configs.Length - 1])) { # # } } #这种写法把文本压缩到最小换行完全靠代码逻辑来管理。它看起来不如前面那种直观但是在处理复杂格式比如生成 CSV、配置文件时控制力明显更强。不过初学者暂时不用追求这种写法先能跑通循环再逐步加入对精细换行的控制。3.5 生成结果进阶把循环逻辑封装进模板方法随着循环逻辑变复杂你会开始觉得在模板主体里堆 C# 代码不舒服。此时可以用# ... #定义一个类特性控制块模板主体调用起来会简洁许多。# template languageC# # # output extension.txt # # var configs new[] { new { Version 1.0, MerchantId 100023, Format JSON }, new { Version 1.0, MerchantId 100024, Format XML }, new { Version 1.1, MerchantId 100025, Format JSON }, }; WriteLine(START_BATCH); foreach (var config in configs) { WriteConfig(config.Version, config.MerchantId, config.Format); } # # private void WriteConfig(string version, string merchantId, string format) { # MERCHANT_ID# merchantId # VERSION# version # FORMAT# format # # } #看懂这段代码的关键在于#代码块里的内容是往模板生成的类里追加成员而不是普通的流程控制代码。你在主体里调用WriteConfig()它就执行一次输出。用这种方法封装的好处是当循环体和输出逻辑一多模板主体可读性会明显提升而且方法还能在多个循环里复用。有一点需要留意#块里如果包含文本块这些文本块属于“单个方法的一部分”不能在任何普通方法外面写文本。也就是说#里面定义的只能是类成员不能在它里面直接输出像普通文本块那样的顶层内容。换句话说辅助方法里可以有文本块输出但所有这些输出逻辑必须在方法的包裹之下。4. 常见问题与排查技巧实录4.1 生成结果乱码八成是编码问题乱码的问题我前面提过一次这里再单独拎出来说因为它在中文环境里出现概率极高。具体表现是生成的文件里中文变成了“锟斤拷”或“烫烫烫”之类的鬼畜字符。根本原因是.tt文件本身没有以 UTF-8 带 BOM 的格式保存引擎读取模板时按系统默认编码解析中文字符串在代码块里定义时就已经坏了。解决的步骤很简单用 Visual Studio 打开.tt文件。菜单栏 - 文件 - 另存为。点击“保存”按钮旁边的小箭头 - 编码保存。在编码列表里选择“Unicode (UTF-8 带签名) - 代码页 65001”。保存后重新运行模板。这个对策治标也治本。治标是说它把当前模板文件的编码矫正了治本是说你将这个文件签入版本库后其他人拉下来打开的也是带 BOM 的不会再踩同一个坑。4.2 输出文件不更新或生成了一个错误的文件有段时间我改了模板后CtrlS 保存旁边生成的文件却纹丝不动。查了才发现问题是解决方案里存在两个.tt文件名一样但路径不同的模板Visual Studio 有时候会在输出文件重名时静默不报错只在输出窗口留下一行不起眼的警告。排查方法看解决方案资源管理器里生成后的文件是否确实嵌套在正确模板下。看错误列表和输出窗口有没有警告信息。确认模板所在项目的“自定义工具”状态正常。另外一个坑是.tt文件如果“被排除在项目外”或者在一个未加载的项目文件夹里右键菜单的“运行自定义工具”可能完全不出现保存也不会触发生成。检查文件是否在项目中看图标是否实心即可。4.3 引用了程序集但模板里却找不到类型这是新手最容易遇到的问题。你在模板里写了ConfigurationManager也在# assembly #指令里写了System.Configuration可一运行就报“名称 ConfigurationManager 不存在”。我查了多次之后发现某些版本的 Visual Studio 中# assembly #指令的name属性确实可以直接写程序集名比如System.Configuration但若该程序集没有位于引擎默认的探测路径下就得改成给完整物理路径。比如项目里 Dll 在packages目录下写成# assembly name$(SolutionDir)\packages\SomePackage\lib\netstandard2.0\SomePackage.dll #这里的$(SolutionDir)是 T4 支持的属性宏能被引擎自动展开。如果你不清楚某个 DLL 的确切位置一个便捷的办法是直接用 Visual Studio 自带的“代码片段”工具或先在模板里写一行错误的引用再在错误信息里看它给出的“已尝试路径”提示按那个提示把正确路径补上。这类问题还有个隐藏放大器T4 引擎运行的是.NET Framework 版本它不一定认识netstandard目标框架下的库。如果你引用的程序集是netstandard2.0的直接在设计时模板里引用可能会遇到高版本兼容问题。建议在遇到这类情况时优先找有没有.NET Framework版本的库可用或在 csproj 文件里将目标框架改为兼容模式。这个话题展开很深初学者暂时只需知道有这回事即可。4.4 运行模板时出现“编译器版本”相关错误有时候模板代码写得很顺可一运行报错说C# 7.0 语法不可用明明你的项目已经用了很新的 C# 版本。这是因为 T4 引擎的设计时编译默认使用老版本的编译器规则。你需要启用较新的语言版本。在模板顶部加指令# template languageC# hostspecifictrue #然后在项目的.csproj文件中为 T4 模板单独指定编译参数或者安装较新的Microsoft.VisualStudio.TextTemplating.Compiler包。网上讨论得比较多的一种操作是在 csproj 中加一个T4LangVersion的 MSBuild 属性PropertyGroup T4LangVersionlatest/T4LangVersion /PropertyGroup这个属性不是所有项目模板默认自带的。如果是 SDK 风格的项目建议直接尝试升级项目里关联的TextTransform工具版本。不过在系列第一篇里我不会建议你把过多精力放在和编译器版本的博弈上——毕竟刚开始只需要写很基础的语法老版本足够用了。等以后真的需要在模板里使用模式匹配或元组拆解这些新语法特性时再来查这块解决方案不迟。4.5 模板输出多了空行格式全乱了这也是高频疑难杂症。具体表现是循环输出时每条记录之间总是多出换行或者第一行前面莫名其妙有个空行。T4 引擎对“文本块”的换行符号处理是逐字逐字保留的。你在.tt中怎么换行输出就怎么换行。解决办法也不复杂从根源上理解两种空行来源模板文件里的物理空行会原样保留。代码块里的\n字符串常量也会作为换行输出。如果你不希望有额外空行可以在该处把文本块和代码块压缩成一行例如# foreach(var item in list) { ## item ## } #这样模板中没有任何物理换行输出就不会出现多余的换行。代价是模板看起来会比较紧凑牺牲一点可读性。我个人的习惯是优先保格式再用注释块说明意图宁可模板丑一点也不让生成结果修修补补。4.6 错误定位到“文本块”内部不知从何下手有时候错误信息是Transformation error: 文本模板中未结束的块。这种错误十有八九是因为你在某个代码块里漏写了#导致引擎把后面的内容都当成代码块的一部分在解析。检查方式是从模板头部开始按顺序数一遍#和#是否成对。还有个页码容易错就是代码块里的字符串恰好包含#导致引擎提前终止了代码块解析。这种情况下可以在字符串里用\u0023替代哈希字符。比如你想输出一段 C# 代码内容里包含预处理器指令#region直接写在文本块里没问题但如果写在# #region #里引擎会先解析#后跟#region它不会认为#region里的#是结束符。真正危险的是字符串里含有#的组合比如This is # a test这种情况必须转义处理。总之遇到解析层面的报错时不要慌先检查成对的块符号再用二分法排除——注释掉一半代码看错误是否消失。这个方法虽然原始但确实有效。4.7 常见问题速查表症状可能原因解决手段生成结果乱码.tt文件非 UTF-8 BOM 编码另存为 UTF-8 带签名输出文件没更新模板被排除出项目/自定义工具为空确认文件在项目中右键运行自定义工具找不到某个类型# assembly #引用路径不对改用完整路径使用$(SolutionDir)宏编译模板报 C# 语法版本低默认编译器版本较旧后续升级 T4 编译器或项目配置 T4LangVersion输出多出空行文本块物理换行被保留压缩文本块与代码块或用代码控制换行模板报“未结束的块”#和#不配对或字符串包含#检查成对对字符串中的#转义输出文件生成在奇怪的位置自定义工具的输出路径被修改检查.tt文件所在项目的生成设置5. 实操总结与个人体会我在带新人上手 T4 的时候最常说的一句话是不要一开始就追求“万能模板”。先做出一个只能生成三条记录的.txt再一步步把数据源从内嵌数组换成配置文件再换成外部传入的 Entity 类型每走一步都能得到即时反馈这个过程中的理解深度比直接抄一个大而全的模板要扎实得多。踩过几次坑之后我形成了一套固定的模板开场白格式无非是三到五行#指令把language、output、必要的assembly和import写好然后再开代码块。这套开场白我在无数项目里重复使用它保证了我不会再犯“忘记声明语言”“忘记控制扩展名”“临时加引用”的低级错误。你如果觉得有用也可以把这段当作自己的模板习惯固定下来。另外想提一个工程层面的建议生成出来的文件默认是会参与项目编译的比如.cs文件但有些生成结果只是中间产物不需要进 Git。我的做法是在.gitignore里加一条规则把某些目录或某些命名的生成结果排除掉。这个做法会让你在团队协作时少很多莫名其妙的冲突——大家改模板生成同一个文件每人都会有各自的本地改动Git 合起来简直是一场灾难。而把生成文件忽略掉改模板即可需要时再生成反而清爽得多。最后再分享一个小技巧如果你在某个模板里发现一段生成逻辑写得特别顺手、复用率很高建议把它拆到一个单独的.ttinclude文件里。用一条# include fileCommonHelpers.ttinclude #指令就能引入项目内所有模板都能共享。这样既保证了逻辑统一又避免了跨模板复制粘贴带来的维护噩梦。下一篇我准备专门讲讲.ttinclude、参数传递和预处理模板的内容如果你把这一篇里的例子都亲手敲了一遍下篇的起点会非常舒服。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑