尧尧爷爷 發表於 2025-8-25 19:35:00

C# 源生成器(Source Generator)入门

<p>C#9 引入了一个强大的机制:源生成器(Source Generator)。通过创建源生成器,我们可以简化大量重复编写的代码,或是减少反射来获得更强的性能以及AOT支持</p>
<p>本文将介绍如何从零开始创建一个最简单的源生成器</p>
<h2 id="创建生成器项目">创建生成器项目</h2>
<p>创建一个SourceGeneratorDemo.Generator项目,目标框架需要设置为.NET Standard 2.0</p>
<blockquote>
<p>这大概由于Visual Studio尚未迁移到.NET Core,Framework最高支持.NET Standard 2.0的项目</p>
</blockquote>
<p>添加<code>Microsoft.CodeAnalysis.Analyzers</code>、<code>Microsoft.CodeAnalysis.CSharp</code>的nuget引用。</p>
<p>添加<code>EnForceExtendedAnalyzerRules</code>属性,强制禁用一些分析器不适用的API,否则IDE会有警告</p>
<p>(此属性的具体作用可以看lindexi大佬的这篇文章)</p>
<pre><code class="language-xml">&lt;PropertyGroup&gt;
        ...
    &lt;EnForceExtendedAnalyzerRules&gt;true&lt;/EnForceExtendedAnalyzerRules&gt;
&lt;/PropertyGroup&gt;
</code></pre>
<p>最终csproj文件如下:</p>
<pre><code class="language-xml">&lt;Project Sdk="Microsoft.NET.Sdk"&gt;

    &lt;PropertyGroup&gt;
      &lt;TargetFramework&gt;netstandard2.0&lt;/TargetFramework&gt;
      &lt;LangVersion&gt;preview&lt;/LangVersion&gt;
      &lt;Nullable&gt;enable&lt;/Nullable&gt;
      &lt;EnForceExtendedAnalyzerRules&gt;true&lt;/EnForceExtendedAnalyzerRules&gt;
    &lt;/PropertyGroup&gt;

    &lt;ItemGroup&gt;
      &lt;PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0"&gt;
      &lt;PrivateAssets&gt;all&lt;/PrivateAssets&gt;
      &lt;IncludeAssets&gt;runtime; build; native; contentfiles; analyzers; buildtransitive&lt;/IncludeAssets&gt;
      &lt;/PackageReference&gt;
      &lt;PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" /&gt;
    &lt;/ItemGroup&gt;

&lt;/Project&gt;
</code></pre>
<h2 id="编写源生成器">编写源生成器</h2>
<p>创建<code>SampleGenerator.cs</code>,添加<code></code>特性并实现<code>IIncrementalGenerator</code>接口</p>
<blockquote>
<p>旧的源生成器<code>ISourceGenerator</code>在每次代码有更改时都会扫描整个语法树,开销很大。而新的增量生成器<code>IIncrementalGenerator</code>通过管道等方式遴选需要扫描的代码,大大减少生成开销。</p>
<p>因此这里我们选择<code>IIncrementalGenerator</code>进行实现</p>
</blockquote>
<pre><code class="language-csharp">
public class SampleGenerator : IIncrementalGenerator
{
        public void Initialize(IncrementalGeneratorInitializationContext context)
    {
      //在这里编写生成器逻辑
    }
}
</code></pre>
<p>我们希望生成的代码如下,这里就放在一个常量中:</p>
<pre><code class="language-csharp">private const string HelloWorld =
    """
    //加上此行以防止编译器进行不必要的代码分析,避免出现警告
    //&lt;auto-generated&gt;
    using System;
    namespace SourceGeneratorDemo.Generator;

    public class HelloWorld
    {
      public static void SayHello()
      {
            Console.WriteLine("Hello, World!");
      }
    }
    """;
</code></pre>
<p>现在来编写<code>Initialize</code>方法,这是源生成器的核心部分:</p>
<pre><code class="language-csharp">public void Initialize(IncrementalGeneratorInitializationContext context)
{
    //在编译时生成源代码
    //HelloWorld.g.cs就是生成代码的文件名称
    context.RegisterPostInitializationOutput(ctx =&gt; ctx.AddSource("HelloWorld.g.cs",SourceText.From(HelloWorld, Encoding.UTF8)));
}
</code></pre>
<p>所有代码如下:</p>
<pre><code class="language-csharp">using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;

namespace SourceGeneratorDemo.Generator;


public class SampleGenerator : IIncrementalGenerator
{
    private const string HelloWorld =
      """
      //加上此行以防止编译器进行不必要的代码分析,避免出现警告
      //&lt;auto-generated&gt;
      using System;
      namespace SourceGeneratorDemo.Generator;

      public class HelloWorld
      {
            public static void SayHello()
            {
                Console.WriteLine("Hello, World!");
            }
      }
      """;

    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
      //在编译时生成源代码
      context.RegisterPostInitializationOutput(ctx =&gt;
            ctx.AddSource("HelloWorld.g.cs",SourceText.From(HelloWorld, Encoding.UTF8)));
    }
}
</code></pre>
<h2 id="在其他项目中引用源生成器">在其他项目中引用源生成器</h2>
<p>创建一个控制台项目SourceGeneratorDemo.Console,设为我们的启动项目</p>
<p>添加项目引用后,我们还要在csproj文件里做出以下调整:</p>
<pre><code class="language-xml">&lt;ProjectReference Include="..\SourceGeneratorDemo.Generator\SourceGeneratorDemo.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/&gt;
</code></pre>
<p><code>OutputItemType</code>属性说明源生成器是作为一个分析器(Analyzer)引入项目;</p>
<p><code>ReferenceOutputAssembly</code>阻止源生成器的程序集复制到输出文件夹,因为其只在编译时起作用</p>
<p>重新生成解决方案后,以Rider为例,我们可以在项目的<code>依赖项&gt;.NET 9.0(取决于当前项目的.NET版本)&gt;SourceGeneratorDemo.Generator.SampleGenerator</code>中找到刚刚生成的<code>HelloWorld.g.cs</code>,其内容与刚刚常量中的代码完全一致</p>
<p><img src="https://img2024.cnblogs.com/blog/3275657/202508/3275657-20250825193326816-1296451931.png" alt="image" loading="lazy"></p>
<p>若使用Visual Studio 2022,在项目的<code>依赖项&gt;分析器&gt;SourceGeneratorDemo.Generator&gt;SourceGeneratorDemo.Generator.SampleGenerator</code>中也可以找到相同文件</p>
<p><img src="https://img2024.cnblogs.com/blog/3275657/202508/3275657-20250825193411946-773835033.png" alt="image" loading="lazy"></p>
<p>现在我们就可以在Program.cs引用生成的内容了:</p>
<pre><code class="language-csharp">using SourceGeneratorDemo.Generator;
HelloWorld.SayHello();
</code></pre>
<p>编译成功即可看到输出</p>
<p><img src="assets/CSharp%20%E6%BA%90%E7%94%9F%E6%88%90%E5%99%A8(Source%20Generator)%E5%85%A5%E9%97%A8/03.png" alt="" loading="lazy"></p>
<h2 id="源码">源码</h2>
<p>https://github.com/zxbmmmmmmmmm/SourceGeneratorDemo</p>
<h2 id="引用">引用</h2>
<p>Roslyn 分析器 EnforceExtendedAnalyzerRules 属性的作用</p>
<p>源生成器:根据需要自动生成机械重复代码 | 扑克博客</p><br><br>
来源:https://www.cnblogs.com/BettaFish/p/19057608
頁: [1]
查看完整版本: C# 源生成器(Source Generator)入门