介紹
源生成器是 C# 開發人員可以編寫的一種新元件,允許執行兩個主要操作:
- 檢索表示正在編譯的所有使用者程式碼的編譯物件。 可以檢查此物件,並且可以編寫適用於正在編譯的程式碼的語法和語義模型的程式碼,就像現在使用分析器一樣。
- 生成可在編譯過程中新增到編譯物件的 C# 原始檔。 也就是說,在編譯程式碼時,可以提供其他原始碼作為編譯的輸入。
結合使用這兩項操作能充分發揮源生成器的強大功能。 可以使用編譯器在編譯時構建的豐富後設資料檢查使用者程式碼。 然後,生成器將 C# 程式碼傳送回基於已分析資料的同一編譯。 如果你熟悉 Roslyn 分析器,可以將源生成器視為可發出 C# 原始碼的分析器。
源生成器作為編譯階段執行,如下所示:
源生成器是由編譯器與任何分析器一起載入的 .NET Standard 2.0 程式集。 它在可以載入和執行 .NET Standard 元件的環境中使用。
注意:目前只能用 .NET Standard 2.0 程式集作源生成器。
實現Hello Wolrd
接下來開始使用Source Genertor實現我們我HelloWorld程式。
建立專案
建立一個HelloWorld的控制檯專案。
將Program改成部分類。並新增一個Hello的部分方法。
namespace HelloWorld
{
partial class Program
{
static void Main(string[] args)
{
Hello("Generated Code");
}
static partial void Hello(string name);
}
}
接下來建立一個netstandard2.0的類庫。
命名成HelloWorld.Analysis。新增依賴Microsoft.CodeAnalysis.CSharp和Microsoft.CodeAnalysis.Analyzers。需要設定PrivateAssets=“all”。
完整配置如下:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
</ItemGroup>
</Project>
這裡需要注意的是Microsoft.CodeAnalysis.CSharp不宜使用太高版本,太高版本可能會出現無法正常生成程式碼的情況。
在HelloWorld專案中新增HelloWorld.Analysis的專案依賴。並設定OutputItemType="Analyzer" ReferenceOutputAssembly="false"
如下所示:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\HelloWorld.Analysis\HelloWorld.Analysis.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
</Project>
實現Generator
在HelloWorld.Analysis中新增HelloSourceGenerator類。繼承並實現ISourceGenerator介面。並且需要在類上加上Generator特性標籤。
然後再Exceute中實現我們的程式碼生成邏輯。
using Microsoft.CodeAnalysis;
namespace HelloWorld.Analysis
{
[Generator]
public class HelloSourceGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
var mainMethod = context.Compilation.GetEntryPoint(context.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;
context.AddSource($"{typeName}.g.cs", source);
}
public void Initialize(GeneratorInitializationContext context)
{
}
}
}
在上面程式碼中,透過Compilation獲取Program程式入口的資訊。包括名稱空間,類名等等等。最後AddSource($"{typeName}.g.cs", source);表示我們把程式碼生成到.g.cs字尾的檔案中。
編譯
接下來啟動編譯專案,在HelloWorld的依賴項的分析器中會出現一個Program.g.cs檔案。
雙擊開啟可以看到生成的程式碼。並且會提示該檔案是自動生成的,無法編輯。
可以看到,檔案中我們實現了部分類Program中的部分方法Hello。
執行專案
啟動專案,可以看到我們成功輸出由Source Genertor生成的Hello方法的實現。
注意事項
細心的同學可能會看到我們編譯的時候會出現一個警告:
warning RS1036: “HelloWorld.Analysis.HelloSourceGenerator”: 包含分析器或源生成器的專案應指定屬性“
建議我們在專案中新增EnforceExtendedAnalyzerRules的屬性。
當我們新增這個屬性後這個警告就會消失。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
</ItemGroup>
</Project>
設定 EnforceExtendedAnalyzerRules 為 true 的作用就是提供 API 禁用分析功能,防止寫出分析器不支援的程式碼。設定 EnforceExtendedAnalyzerRules 為 true 時,有部分的 API 將會被提示不可用。具體API可以看: https://raw.githubusercontent.com/dotnet/roslyn-analyzers/2b6ab8d727ce73a78bcbf026ac75ea8a7c804daf/src/Microsoft.CodeAnalysis.Analyzers/Core/AnalyzerBannedSymbols.txt
Debug
前面我們直接編譯就生成了程式碼,打斷點也不會觸發。那麼我們如何除錯SourceGenerator呢?
可以使用Debugger.Launch();來觸發除錯。
在我們的執行程式碼中加入這一行。在編譯時會觸發除錯提示。
using Microsoft.CodeAnalysis;
using System.Diagnostics;
namespace HelloWorld.Analysis
{
[Generator]
public class HelloSourceGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
Debugger.Launch(); //觸發Debug
var mainMethod = context.Compilation.GetEntryPoint(context.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;
context.AddSource($"{typeName}.g.cs", source);
}
public void Initialize(GeneratorInitializationContext context)
{
}
}
}
加入程式碼後,重新執行專案編譯操作。會彈出Debugger提示。
點選OK即可進入除錯模式。
如果不需要點選Cancel則可以跳過。
結語
本文初步的瞭解了SourceGenerator的功能以及使用和除錯的方式,後面的文章我們再來逐步深入學習。
文章程式碼倉庫地址https://github.com/fanslead/Learn-SourceGenerator