學習Source Generators之IIncrementalGenerator

前面我們用ISourceGenerator來實現代碼生成。但是在官方的介紹中有這麼一個警告:Warning: Source generators implementing ISourceGenerator have been deprecated in favor of incremental generators.
意思是實現ISourceGenerator的源生成器已被棄用,取而代之的是增量生成器。
image.png

那麼本文將會介紹IIncrementalGenerator來實現我們的代碼生成。後續的文章我們都只會使用IIncrementalGenerator來介紹和學習。

介紹

在官方的介紹中是這樣介紹的:增量生成器是一種新的 API,與源生成器一起存在 ,允許用戶指定可由託管層以高性能方式應用的生成策略。

High Level Design Goals

  • 允許使用更細粒度的方法來定義生成器
  • 縮放源生成器以支持 Visual Studio 中的“Roslyn/CoreCLR”縮放項目
  • 利用細粒度步驟之間的緩存來減少重複工作
  • 支持生成更多項目而不僅僅是源文本
  • ISourceGenerator與基礎實現並存

使用IIncrementalGenerator實現HelloWorld

我們將HelloSourceGenerator的繼承接口修改成IIncrementalGenerator並實現Initialize方法。

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;

namespace HelloWorld_IncrementalGenerator.Analysis
{
    [Generator]
    public class HelloSourceGenerator : IIncrementalGenerator
    {
        public void Initialize(IncrementalGeneratorInitializationContext context)
        {
            var compilation = context.CompilationProvider;
            context.RegisterSourceOutput(compilation, (sourceProductionContext, c) =>
            {
                var mainMethod = c.GetEntryPoint(sourceProductionContext.CancellationToken);

                string source = $@"// <auto-generated/>
using System;

namespace {mainMethod.ContainingNamespace.ToDisplayString()}
{{
    public static partial class {mainMethod.ContainingType.Name}
    {{
        static partial void Hello(string name) =>
            Console.WriteLine($""Hello: '{{name}}'"");
    }}
}}
";
                var typeName = mainMethod.ContainingType.Name;

                var sourceText = SourceText.From(source, Encoding.UTF8);
                sourceProductionContext.AddSource($"{typeName}.g.cs", sourceText);
            });
        }
    }
}

其他地方完全無需改動。啓動編譯。可以看到成功生成代碼。
image.png
啓動項目:
image.png
可以看到效果是一模一樣的。

基於管道的執行

增量生成器不是使用用Execute方法,而是定義不可變的執行管道作爲初始化的一部分。該 Initialize方法接收一個實例, 生成器使用IncrementalGeneratorInitializationContext實例來定義一組轉換。
輸入數據以不透明數據源的形式提供給管道,可以是IncrementalValueProvider或IncrementalValuesProvider(注意複數值),其中T是提供的輸入數據的類型。
上面我我們使用的CompilationProvider類型就是就是IncrementalValueProvider。這個Provider提供了我們對Compilation的訪問,用於獲取程序集的數據。

目前官方提供可用的Providers有如下幾種:

  • CompilationProvider
  • AdditionalTextsProvider
  • AnalyzerConfigOptionsProvider
  • MetadataReferencesProvider
  • ParseOptionsProvider

比如我們需要讀取文件信息,可以使用AdditionalTextsProvider。
我們可以使用Select對Provider進行數據的轉換。這個方法跟Linq有點像,但是不是Linq。
下面是官方的一個示例,表示對數據的一個轉換處理。transformed和prefixTransform也都是IncrementalValuesProvider實例,表示轉換的結果,而不是數據本身。

// get the additional text provider
IncrementalValuesProvider<AdditionalText> additionalTexts = initContext.AdditionalTextsProvider;

// apply a 1-to-1 transform on each text, which represents extracting the path
IncrementalValuesProvider<string> transformed = additionalTexts.Select(static (text, _) => text.Path);

// transform each extracted path into something else
IncrementalValuesProvider<string> prefixTransform = transformed.Select(static (path, _) => "prefix_" + path);

注意:在使用AdditionalTextsProvider的時候。需要在項目文件中添加AdditionalFiles。
比如:

<ItemGroup>
  <AdditionalFiles Include="*.json" />
</ItemGroup>

否則生成代碼的操作不會被觸發。

結語

本文章簡單介紹了使用IIncrementalGenerator來進行代碼生成的方式以及他的執行邏輯。下一篇我們將詳細介紹IncrementalValueProvider以及使用。
本文代碼倉庫地址https://github.com/fanslead/Learn-SourceGenerator

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章