编写你的专属 MSBuild C# 代码生成器:在保存文件时自动实时生成你的代码

我之前的博客中有介绍如何在项目中生成额外的代码,也有介绍制作一个生成代码的 NuGet 包。而本文是在此基础上更进一步,可以让生成代码变成实时的;更准确的说,是在保存文件时即生成代码,而无需完整编译一次项目。


一天,头像全白昵称空格的“wuweilai”童鞋问我为什么 GRPC 的 NuGet 包能自动在 .proto 文件保存时更新生成的代码,怎么才能做到像它那样。然后,我研究了下 Grpc.Tools 包里的代码,外加跟他反复讨论,摸清了自动生成代码的方法。

背景知识

本文的知识非常简单,如果只是希望知道怎么实时生成代码的话,把本文后面的代码复制一下就可以了。但如果希望完整了解基于 MSBuild 生成代码的原理,你可以需要了解以下知识或教程:

准备项目

我们创建一个全新的项目,用来了解如何实时生成代码。

如下图,就是个普通的控制台应用程序。我额外生成了一个 Test.txt 文件,里面什么也没有。我们即将实现的是:在保存 Test.txt 文件时,会立即执行我们的编译流程,这样,我们便能基于 Test.txt 来实时生成一些代码。

一个简单的项目结构

最简单的自动生成代码的逻辑

现在,我们打开项目 csproj 文件(双击项目名称即可打开编辑这个文件):

  <Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
      <OutputType>Exe</OutputType>
      <TargetFramework>net5.0</TargetFramework>
    </PropertyGroup>

+   <!-- 将项目中的所有 txt 文件搜集起来,用 WalterlvDemoFile 集合存起来。-->
+   <ItemGroup>
+     <WalterlvDemoFile Include="**\*.txt" Generator="MSBuild:Compile" />
+   </ItemGroup>

+   <!-- 注册 WalterlvDemoFile 项为一个 Item,这样它的通用属性就能被识别了。 -->
+   <ItemGroup>
+     <AvailableItemName Include="WalterlvDemoFile" />
+   </ItemGroup>

+   <!-- 随便写一个 Target,在编译之前做些什么。 -->
+   <Target Name="WalterlvDemoTarget" BeforeTargets="BeforeCompile">
+     <Exec Command="winver" />
+   </Target>

  </Project>

我把新增的代码高亮出来了。如果你想复制到你的项目里,记得去掉行首的所有 + 号。

等你复制到项目里之后,试着在 Test.txt 文件里面随便写点什么,然后保存。你会发现……呃……弹出了一个 Windows 版本号窗口……

最简代码解读

  1. 我们定义了一个 Target,名为 WalterlvDemoTarget(随便取的名字),并要求在 BeforeCompile 这个 Target 执行之前执行。
  2. 我们定义了一个 WalterlvDemoFile 项,这是随便取的名字,是为了搜集 *.txt 文件。
  3. 我们在 WalterlvDemoFile 里指定 GeneratorMSBuild:Compile
    • 对于已知的项(Item)来说,Generator 属性是 MSBuild 编译时的一个已知元数据(Metadata),其作用为当此文件改变时,会执行一个指定的 Target
    • 我们将其指定为 MSBuild:Compile,即指定为 MSBuild 内置的一个 Target Compile,意为执行一次编译
  4. 然而,WalterlvDemoFile 并不是已知的项,所以我们还需要额外将 WalterlvDemoFile 添加到 AvailableItemName 集合里。

延伸

在上面那个最简的 Demo 中,我们弹出了个 Windows 版本号,这真的只是为了让你立刻注意到某个代码执行了。当然真正生成代码肯定不会是这样的弹窗。

不过,你可以从我的其他博客里找到很多生成代码的方法,比如这篇……还有这篇……还有这这这篇……


参考资料

本文会经常更新,请阅读原文: https://blog.walterlv.com/post/msbuild-generate-code-when-file-is-saved.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

如果你想持续阅读我的最新博客,请点击 RSS 订阅,或者前往 CSDN 关注我的主页

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 吕毅 (包含链接: https://blog.walterlv.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 ([email protected])