.net Source Generators的基本使用 电脑版发表于:2024/9/25 16:12 ![.netcore](https://img.tnblog.net/arcimg/hb/c857299a86d84ee7b26d181a31e58234.jpg ".netcore") >#.net Source Generators的基本使用 [TOC] Source Generators简介 ------------ tn2>Source Generators是一项C#编译功能,使C#开发人员能够在编译用户代码时进行检查,并动态生成新的C#源文件,以添加到用户的编译中。 通过这种方式,你的代码可以在编译过程中运行并检查你的程序以生成与其余代码一起编译的其他源文件。 允许执行两个主要操作 ------------ tn2>1.检索表示正在编译的所有用户代码的编译对象。可以检查此对象,并且可以编写适用于正在编译的代码语法和语义模型的代码,就像现在使用分析器一样。 2.生成可在编译过程中添加到编译对象的C#源文件。也就是说,在编译代码时,可以提供其他源代码作为编译的输入。 ![](https://img.tnblog.net/arcimg/hb/7372849355844b96a1f431465d732591.png) 简单示例 ------------ tn2>首先创建一个`LearningSourceGenerators`使用`.net8`的控制台项目。 然后安装`Microsoft.CodeAnalysis.Analyzers`与`Microsoft.CodeAnalysis.CSharp`两个包。 ```xml <ItemGroup> <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4"> <PrivateAssets>all</PrivateAssets> <!--<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>--> </PackageReference> <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" PrivateAssets="all" /> </ItemGroup> ``` tn2>将 Program 类替换为以下代码。 ```csharp namespace LearningSourceGenerators; partial class Program { static void Main(string[] args) { HelloFrom("Generated Code"); } static partial void HelloFrom(string name); } ``` tn2>然后创建一个`LearningSourceGenerators.Tools`的项目,框架版本为`.NET Standard 2.0`。 同样需要安装`Microsoft.CodeAnalysis.Analyzers`与`Microsoft.CodeAnalysis.CSharp`两个包。需要添加上`PrivateAssets="all"`。 tn>目前 .NET Standard 2.0 程序集只能用作源生成器。 tn2>创建一个名为`HelloSourceGenerator.cs`的新 C# 文件,该文件指定你自己的源生成器。 并添加以下内容: ```csharp [Generator] public class HelloSourceGenerator : ISourceGenerator { public void Execute(GeneratorExecutionContext context) { // Find the main method var mainMethod = context.Compilation.GetEntryPoint(context.CancellationToken); // Build up the source code string source = $@"// <auto-generated/> using System; namespace {mainMethod.ContainingNamespace.ToDisplayString()} {{ public static partial class {mainMethod.ContainingType.Name} {{ static partial void HelloFrom(string name) => Console.WriteLine($""Generator says: Hi from '{{name}}'""); }} }} "; var typeName = mainMethod.ContainingType.Name; // Add the source code to the compilation context.AddSource($"{typeName}.g.cs", source); } public void Initialize(GeneratorInitializationContext context) { } } ``` tn2>从 `context` 对象中,我们可以访问编译的入口点或 `Main` 方法。 `mainMethod` 实例是一个 `IMethodSymbol`,它表示一个方法或类似方法的符号(包括构造函数、析构函数、运算符或属性/事件访问器)。 `Microsoft.CodeAnalysis.Compilation.GetEntryPoint` 方法返回程序的入口点的 `IMethodSymbol`。 其他方法使你可以查找项目中的任何方法符号。在此对象中,我们可以推理包含的命名空间(如果存在)和类型。 此示例中的 `source` 是一个内插字符串,它对要生成的源代码进行模板化,其中内插的缺口填充了包含的命名空间和类型信息。 使用提示名称将 `source` 添加到 `context`。 对于此示例,生成器创建一个新的生成的源文件,其中包含控制台应用程序中 partial 方法的实现。 可以编写源生成器来添加任何喜欢的源。 tn>GeneratorExecutionContext.AddSource 方法中的 hintName 参数可以是任何唯一名称。 通常为该名称提供显式 C# 文件扩展名,例如 `.g.cs` 或 `.generated.cs`。 该文件名有助于将文件标识为正在生成源。 tn2>然后我们添加`LearningSourceGenerators.Tools`项目到我们的`LearningSourceGenerators`控制台项目中。 ```csharp <ItemGroup> <ProjectReference Include="..\LearningSourceGenerators.Tools\LearningSourceGenerators.Tools.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> </ItemGroup> ``` tn2>须手动编辑以包含 `OutputItemType` 和 `ReferenceOutputAssembly` 属性。 `OutputItemType="Analyzer"`:表示引用项目的输出是一个`Roslyn`编译器分析器,而不是普通的项目依赖项。 `ReferenceOutputAssembly="false"`:表示该项目不会编译到当前项目的输出程序集中。 接下来我们开始运行程序。 ![](https://img.tnblog.net/arcimg/hb/6630406cf6224f78875ad95ba97c2487.png) ![](https://img.tnblog.net/arcimg/hb/a672780d4a174f18b0ff184b0f940528.png) tn2>程序执行完成,输出了相关内容。 我们在调试的时候发现它生成的代码文件在`C`盘下面。 如何将它源代码保存到本地呢? 源代码保存到本地 ------------ tn2>可以通过在`LearningSourceGenerators.csproj`中设置`EmitCompilerGeneratedFiles`属性完成。 ```xml <PropertyGroup> <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> </PropertyGroup> ``` tn2>完成此配置之后,将会自动将源代码生成器所生成的代码存放到本地文件夹里面。 调试下的输出大概是 `obj\Debug\net8.0\` 等类似的文件夹里。 ![](https://img.tnblog.net/arcimg/hb/4e564c7695ee42fe9532408e4699e4c3.png) ![](https://img.tnblog.net/arcimg/hb/ab2574dc2dde4be8a24ce414e58bdc2e.png) tn2>如果期望自己指定保存的文件夹,可以自行设置 `EmitCompilerGeneratedFiles` 属性,如以下代码: ```csharp <PropertyGroup> <CompilerGeneratedFilesOutputPath>Generated\$(TargetFramework)</CompilerGeneratedFilesOutputPath> </PropertyGroup> ``` tn2>以上代码之所以拼接上 TargetFramework 是因为期望默认处理多框架的文件冲突问题,源代码生成器会在多框架下分别执行,为每个框架生成独立的代码。如果在多框架项目下没有配置加上 TargetFramework 将会造成生成的源代码存放的文件冲突 上面代码添加之后,预计将会导致构建不通过,一般的保存信息如下 ![](https://img.tnblog.net/arcimg/hb/4399176e21b54d6497f4f1918847b7d2.png) tn2>这是因为设置放在 `Generated\$(TargetFramework)` 会被 csproj 默认作为源代码引用,导致原本源代码生成器生成的代码已经在内存里面被引用一次,现在源代码生成器输出的文件又被再次引用,导致了最终构建不通过 解决方法就是去掉对 `CompilerGeneratedFilesOutputPath` 的文件的引用,确保只有引用源代码生成器在内存的一份代码,如以下代码: ```xml <ItemGroup> <!-- 添加了内存里面的文件,不应该添加磁盘的,否则添加两份 --> <Compile Remove="$(CompilerGeneratedFilesOutputPath)/**/*.cs" /> </ItemGroup> ``` ![](https://img.tnblog.net/arcimg/hb/23a46fc15d9e49668ded1851c81d9f25.png) ![](https://img.tnblog.net/arcimg/hb/468aee524f344fefae0ea6706c0a6944.png) ![](https://img.tnblog.net/arcimg/hb/04fee30327c046149cccb41194206cd9.png) 发布NuGet Package ------------ tn2>从源生成器创建`NuGet`包类似于标准库的`NuGet`包,但`NuGet`包的内容布局不同。具体来说,您必须: ** + ** 确保生成输出最终位于 `NuGet` 包的 `analyzers/dotnet/cs` 文件夹中。 ** + ** 确保`dll`不会出现在`NuGet`包的`normal`文件夹中。 对于第一点,请确保你的项目中有以下内容: ```xml <ItemGroup> <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" /> </ItemGroup> ``` tn2>这将确保源生成器程序集打包到`NuGet`包中的正确位置,以便编译器将其作为分析器/源生成器加载。 你还应该将该属性设置为,以便使用项目不会获得对源生成器`dll`本身的引用: ```xml <PropertyGroup> <IncludeBuildOutput>false</IncludeBuildOutput> </PropertyGroup> ``` ![](https://img.tnblog.net/arcimg/hb/938a01a204604d14a3abd265a35c8003.png) tn2>开始打包: ![](https://img.tnblog.net/arcimg/hb/6f114328c7c7493b8f580b5d9765d769.png) tn2>然后创建一个`LearningSourceGenerators.TestConsole`新的项目,并且在本地添加上源然后安装好这个包。 ![](https://img.tnblog.net/arcimg/hb/248daea119874c50b2f50e1ffeae4c13.png) tn2>添加代码。 ```csharp namespace LearningSourceGenerators.TestConsole; partial class Program { static void Main(string[] args) { HelloFrom("Generated Code"); } static partial void HelloFrom(string name); } ``` tn2>然后我们也能看见代码生成器那儿已经生成好了代码。 ![](https://img.tnblog.net/arcimg/hb/bdcffeccd2e142b19a9927944d33554a.png) ![](https://img.tnblog.net/arcimg/hb/1c37242768d94aee9870bd062e960c19.png)