前言
嗨,大家好!
今天我們來聊一聊 C# 里的源代碼生成器,一個有趣的代碼生成工具。
源代碼生成器(Source Generators)是 C# 9.0 引入的一項強(qiáng)大功能,允許你在編譯時動態(tài)生成源代碼。
這意味著,你可以編寫代碼來自動生成其他代碼,從而減少手動重復(fù)的工作。
這個過程在編譯階段發(fā)生,生成的代碼會在編譯輸出中包含,從而使你的類庫或應(yīng)用程序更輕便、更可維護(hù)。
首先我們通過一個 Step By Step 例子來感受一下它的魅力吧!
Step By Step 例子
我們來創(chuàng)建一個源生成器,它可以自動為一個類生成一個 ToString
方法,方便快速打印統(tǒng)一的對象信息
1. 創(chuàng)建項目
在 Visual Studio 2022 IDE 中,創(chuàng)建一個新的類庫項目,命名為 SourceGen
選擇 .NET Standard 2.0 版本,如圖:
2. 添加 NuGet 包
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
注意:
不要選擇太高的版本,否則可能會出現(xiàn)編譯器版本太高的問題,比如:
分析器程序集“...”引用了編譯器的版本 “4.12.0.0”,該版本高于當(dāng)前正在運(yùn)行的版本 “4.8.0.0”
包 Microsoft.CodeAnalysis.CSharp
已經(jīng)包含了 Microsoft.CodeAnalysis.Analyzers
,所以無需繼續(xù)添加此包
3. 配置 EnforceExtendedAnalyzerRules 規(guī)則
打開項目文件 SourceGen.csproj
,在 PropertyGroup
節(jié)點(diǎn)下手動增加 EnforceExtendedAnalyzerRules
配置,如圖:
4. 創(chuàng)建源生成器
在項目中,創(chuàng)建一個新的類文件,命名為 ToStringGenerator.cs
,并編寫以下代碼:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Text;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespaceSourceGen
{
[Generator]
publicclassToStringGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
var syntaxTrees = context.Compilation.SyntaxTrees;
foreach (var tree in syntaxTrees)
{
var root = tree.GetRoot();
var classes = root.DescendantNodes().OfType<ClassDeclarationSyntax>();
foreach (var classDeclaration in classes)
{
// 獲取類名
var className = classDeclaration.Identifier.Text;
// 獲取類成員
var properties = classDeclaration.Members.OfType<PropertyDeclarationSyntax>();
var propertiesList = string.Join(", ", properties.Select(p => $"{p.Identifier.Text} = {{{p.Identifier.Text}}}"));
// 寫 ToString 方法
// 注意這里用了 partial 修飾符
var toStringMethod = $@"
public partial class {className} {{
public override string ToString() => ""{className} {{{propertiesList}}}"";
}}
";
context.AddSource($"{className}_ToString.g.cs", SourceText.From(toStringMethod, Encoding.UTF8));
}
}
}
public void Initialize(GeneratorInitializationContext context)
{
// 可以在這里初始化任何需要的對象或資源
}
}
}
編譯一下確定代碼沒問題。
5. 創(chuàng)建新控制臺項目
在解決方案中,添加一個新的控制臺項目,命名為 SourceGeneratorSample
6. 引用源代碼生成器項目 SourceGen
注意,引用后,需要打開 SourceGeneratorSample.csproj,修改項目引用節(jié)點(diǎn)內(nèi)容,添加 OutputItemType
和 ReferenceOutputAssembly
配置:
<ProjectReference Include="..\SourceGen\SourceGen.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
7. 定義一個類
在 SourceGeneratorSample
項目中,創(chuàng)建一個簡單的類,例如 Person
:
public partial class Person
{
public string? Name { get; set; }
public int Age { get; set; }
}
注意:
8. 使用源代碼器生成的方法
在 Program.cs
文件中,創(chuàng)建一個 Person
實(shí)例對象并打印它的 ToString
:
var person = new Person { Name = "Alice", Age = 30 };
Console.WriteLine(person.ToString());
9. 運(yùn)行
按 Ctrl+F5 編譯并運(yùn)行程序,你會在控制臺看到如下圖輸出:
優(yōu)勢與劣勢
在上面的例子里,我們?yōu)轫椖恐兴械念惤y(tǒng)一了 ToString
方法的輸出,從中我們可以看出一些源代碼生成器的優(yōu)勢和劣勢:
優(yōu)勢
減少重復(fù)勞動:源代碼生成器可以自動生成一些繁瑣的重復(fù)性代碼,特別是那些不變的基本數(shù)據(jù)結(jié)構(gòu)或方法,比如數(shù)據(jù)傳輸對象(DTO)或?qū)嶓w類。
提高代碼一致性:生成的代碼遵循預(yù)設(shè)的邏輯,可以更好地保持一致性,減少了人為錯誤
保持代碼整潔:將生成的代碼與手寫的代碼分開,主代碼會顯得更加清晰易讀
劣勢
編譯時錯誤難以調(diào)試:因?yàn)榇a是在編譯階段生成的,如果生成的代碼有問題,定位和修復(fù)會相對比較困難
學(xué)習(xí)成本:源代碼生成器的概念和使用方式跟傳統(tǒng)開發(fā)方式差別比較大,需要一定的學(xué)習(xí)時間來掌握其使用方法
可能增加復(fù)雜性:源代碼生成器的實(shí)現(xiàn)需要對語法樹有深入的理解,這可能增加開發(fā)的復(fù)雜性。
版本兼容性:如果使用的源生成器依賴于特定版本的編譯器或框架,升級時可能會有兼容性的問題
總結(jié)
隨著 .NET 8 的發(fā)布,源代碼生成器這一強(qiáng)大特性也逐漸走進(jìn)了更多程序員的視野。
相比傳統(tǒng)的 T4 模板等代碼生成工具,源代碼生成器的最大魅力在于它能在編譯時動態(tài)生成代碼。
這意味著你生成的代碼能立刻融入項目中,無需反復(fù)編譯,就像魔法一樣——寫完代碼馬上就能看到結(jié)果,大大提升了開發(fā)效率。
不過,任何好東西都有它的 “小脾氣”,源代碼生成器也不例外,有時候調(diào)試它很讓人抓狂,特別是如果你對語法樹不太熟悉的話,剛開始接觸時,可能會覺得有點(diǎn)棘手,需要花點(diǎn)時間去適應(yīng)和學(xué)習(xí)。
所以呢,要不要在你的項目里引入這個強(qiáng)大的特性,其實(shí)取決于具體情況。如果你的項目適合并且你能接受一點(diǎn)初期的學(xué)習(xí)成本,那么源代碼生成器絕對會讓你的開發(fā)體驗(yàn)煥然一新。但如果項目時間緊迫或團(tuán)隊成員對它還不夠熟悉,可能就需要再三考慮一下啦。
該文章在 2025/1/6 10:15:30 編輯過