.NET 编译利器 Source Generators 使用指南

679 阅读3分钟

Source Generators简介

Source Generators是一项C#编译功能,使C#开发人员能够在编译用户代码时进行检查,并动态生成新的C#源文件,以添加到用户的编译中。

通过这种方式,你的代码可以在编译过程中运行并检查你的程序以生成与其余代码一起编译的其他源文件。

允许执行两个主要操作

1、检索表示正在编译的所有用户代码的编译对象。可以检查此对象,并且可以编写适用于正在编译的代码语法和语义模型的代码,就像现在使用分析器一样。

2、生成可在编译过程中添加到编译对象的C#源文件。也就是说,在编译代码时,可以提供其他源代码作为编译的输入。

简单示例

首先创建一个LearningSourceGenerators使用.net8的控制台项目。

然后安装Microsoft.CodeAnalysis.AnalyzersMicrosoft.CodeAnalysis.CSharp两个包。

<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>

将 Program 类替换为以下代码。

namespace LearningSourceGenerators;
partial class Program
{
    static void Main(string[] args)
    {
        HelloFrom("Generated Code");
    }
    static partial void HelloFrom(string name);
}

然后创建一个LearningSourceGenerators.Tools的项目,框架版本为.NET Standard 2.0。 同样需要安装Microsoft.CodeAnalysis.AnalyzersMicrosoft.CodeAnalysis.CSharp两个包。需要添加上PrivateAssets="all"

目前 .NET Standard 2.0 程序集只能用作源生成器。

创建一个名为HelloSourceGenerator.cs的新 C# 文件,该文件指定你自己的源生成器。 并添加以下内容:

[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/>
g System;
space {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)
    {
    }
}

context 对象中,我们可以访问编译的入口点或 Main 方法。 mainMethod 实例是一个 IMethodSymbol,它表示一个方法或类似方法的符号(包括构造函数、析构函数、运算符或属性/事件访问器)。

Microsoft.CodeAnalysis.Compilation.GetEntryPoint 方法返回程序的入口点的 IMethodSymbol。 其他方法使你可以查找项目中的任何方法符号。在此对象中,我们可以推理包含的命名空间(如果存在)和类型。

此示例中的 source 是一个内插字符串,它对要生成的源代码进行模板化,其中内插的缺口填充了包含的命名空间和类型信息。 使用提示名称将 source 添加到 context。 对于此示例,生成器创建一个新的生成的源文件,其中包含控制台应用程序中 partial 方法的实现。 可以编写源生成器来添加任何喜欢的源。

GeneratorExecutionContext.AddSource 方法中的 hintName 参数可以是任何唯一名称。 通常为该名称提供显式 C# 文件扩展名,例如 .g.cs.generated.cs。 该文件名有助于将文件标识为正在生成源。

然后我们添加LearningSourceGenerators.Tools项目到我们的LearningSourceGenerators控制台项目中。

<ItemGroup>
    <ProjectReference Include="..\LearningSourceGenerators.Tools\LearningSourceGenerators.Tools.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>

须手动编辑以包含 OutputItemTypeReferenceOutputAssembly 属性。

OutputItemType="Analyzer":表示引用项目的输出是一个Roslyn编译器分析器,而不是普通的项目依赖项。

ReferenceOutputAssembly="false":表示该项目不会编译到当前项目的输出程序集中。 接下来我们开始运行程序。

程序执行完成,输出了相关内容。

我们在调试的时候发现它生成的代码文件在C盘下面。

如何将它源代码保存到本地呢?

源代码保存到本地

可以通过在LearningSourceGenerators.csproj中设置EmitCompilerGeneratedFiles属性完成。

<PropertyGroup>
  <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>

完成此配置之后,将会自动将源代码生成器所生成的代码存放到本地文件夹里面。

调试下的输出大概是 obj\Debug\net8.0 等类似的文件夹里。

如果期望自己指定保存的文件夹,可以自行设置 EmitCompilerGeneratedFiles 属性,如以下代码:

<PropertyGroup>
  <CompilerGeneratedFilesOutputPath>Generated$(TargetFramework)</CompilerGeneratedFilesOutputPath>
</PropertyGroup>

以上代码之所以拼接上 TargetFramework 是因为期望默认处理多框架的文件冲突问题,源代码生成器会在多框架下分别执行,为每个框架生成独立的代码。

如果在多框架项目下没有配置加上 TargetFramework 将会造成生成的源代码存放的文件冲突 上面代码添加之后,预计将会导致构建不通过,一般的保存信息如下

这是因为设置放在 Generated$(TargetFramework) 会被 csproj 默认作为源代码引用,导致原本源代码生成器生成的代码已经在内存里面被引用一次,现在源代码生成器输出的文件又被再次引用,导致了最终构建不通过

解决方法就是去掉对 CompilerGeneratedFilesOutputPath 的文件的引用,确保只有引用源代码生成器在内存的一份代码,如以下代码:

<ItemGroup>
  <!-- 添加了内存里面的文件,不应该添加磁盘的,否则添加两份 -->
  <Compile Remove="$(CompilerGeneratedFilesOutputPath)/**/*.cs" />
</ItemGroup>

发布NuGet Package

从源生成器创建NuGet包类似于标准库的NuGet包,但NuGet包的内容布局不同。

具体来说,您必须:

+ 确保生成输出最终位于 NuGet 包的 analyzers/dotnet/cs 文件夹中。

+ 确保dll不会出现在NuGet包的normal文件夹中。 对于第一点,请确保你的项目中有以下内容:

<ItemGroup>
    <None Include="$(OutputPath)$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>

这将确保源生成器程序集打包到NuGet包中的正确位置,以便编译器将其作为分析器/源生成器加载。

你还应该将该属性设置为,以便使用项目不会获得对源生成器dll本身的引用:

<PropertyGroup>
    <IncludeBuildOutput>false</IncludeBuildOutput>
</PropertyGroup>

开始打包:

然后创建一个LearningSourceGenerators.TestConsole新的项目,并且在本地添加上源然后安装好这个包。

添加代码。

namespace LearningSourceGenerators.TestConsole;
partial class Program
{
    static void Main(string[] args)
    {
        HelloFrom("Generated Code");
    }
    static partial void HelloFrom(string name);
}

然后我们也能看见代码生成器那儿已经生成好了代码。

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。

也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!

优秀是一种习惯,欢迎大家留言学习!

作者:尘叶心繁

出处:tnblog.net/hb/article/details/8472

声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!