还在为重复的代码模板而烦恼?还在手动维护数百个属性访问器?作为.NET开发者,我们经常遇到这样的痛点:大量重复且机械的代码编写工作不仅效率低下,还容易出错。今天要分享的C# Source Generators技术,将彻底改变你的开发方式——让编译器在编译时自动生成代码,告别重复劳动,拥抱高效开发!
本文将从实际问题出发,深入剖析Source Generators的核心逻辑与实现方式,并提供完整的实战代码示例,助你快速掌握这项"让代码自己写代码"的强大技术。
痛点一:模板代码泛滥
C#// 传统方式:每个实体类都需要手写这些重复代码
public class User
{
private string _name;
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
}
// 还有几十个属性要这样写...
}
痛点二:维护成本高昂
当业务模型变化时,相关的序列化、映射、验证代码都需要同步修改,容易遗漏且出错率高。
痛点三:运行时反射性能损耗
传统的动态代码生成依赖反射,在高并发场景下会成为性能瓶颈。
ISourceGenerator
或新版 IIncrementalGenerator
。SyntaxReceiver
或增量管线收集目标节点。StringBuilder
,或更强的 Code DOM 模板(如 Scriban)生成代码。让我们通过一个完整的实例来掌握Source Generators的核心实现。
首先创建Source Generator - AppSourceGeneratorLibrary项目:
XML<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<IncludeBuildOutput>false</IncludeBuildOutput>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<IsRoslynComponent>true</IsRoslynComponent>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
</ItemGroup>
</Project>
属性 | 主要作用 | Source Generator 常见建议 |
---|---|---|
TargetFramework | 指定编译目标 (.NET Standard 版本) | netstandard2.0 或 netstandard2.1(兼容性考虑,我发现2.0要靠谱些,也没深究具体什么问题) |
LangVersion | C# 语言版本 | latest(若需要最新语法) |
Nullable | 启用可空引用分析 | enable(提升代码健壮性) |
IncludeBuildOutput | 是否包含构建产物到打包输出 | false(通常不把 DLL 放到 lib) |
GeneratePackageOnBuild | 是否在构建时生成 NuGet 包 | false(发布时打包) |
IsRoslynComponent | 标记为 Roslyn/编译器相关组件 | true(可选但有用) |
EnforceCodeStyleInBuild | 在构建时强制代码风格 | 视团队策略决定,CI 环境常设 true |
C#using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace AppSourceGeneratorLibrary
{
[Generator]
public class AutoNotifyGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
// 注册语法接收器,用于收集需要处理的类
context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
}
public void Execute(GeneratorExecutionContext context)
{
// 获取语法接收器
if (!(context.SyntaxReceiver is SyntaxReceiver receiver))
return;
// 获取编译信息
var compilation = context.Compilation;
// 为每个标记的类生成代码
foreach (var classDeclaration in receiver.CandidateClasses)
{
var semanticModel = compilation.GetSemanticModel(classDeclaration.SyntaxTree);
var classSymbol = semanticModel.GetDeclaredSymbol(classDeclaration) as INamedTypeSymbol;
if (classSymbol == null) continue;
// 检查是否具有AutoNotify特性
if (HasAutoNotifyAttribute(classSymbol))
{
var source = GenerateAutoNotifyClass(classSymbol, classDeclaration);
context.AddSource($"{classSymbol.Name}_AutoNotify.g.cs",
SourceText.From(source, Encoding.UTF8));
}
}
}
private bool HasAutoNotifyAttribute(INamedTypeSymbol classSymbol)
{
return classSymbol.GetAttributes()
.Any(attr => attr.AttributeClass?.Name == "AutoNotifyAttribute");
}
private string GenerateAutoNotifyClass(INamedTypeSymbol classSymbol, ClassDeclarationSyntax classDeclaration)
{
var namespaceName = classSymbol.ContainingNamespace.ToDisplayString();
var className = classSymbol.Name;
// 获取所有字段
var fields = classDeclaration.Members
.OfType<FieldDeclarationSyntax>()
.Where(f => f.AttributeLists
.Any(al => al.Attributes
.Any(a => a.Name.ToString().Contains("AutoNotify"))))
.SelectMany(f => f.Declaration.Variables)
.ToList();
var sb = new StringBuilder();
// 生成文件头
sb.AppendLine("using System.ComponentModel;");
sb.AppendLine("using System.Runtime.CompilerServices;");
sb.AppendLine();
sb.AppendLine($"namespace {namespaceName}");
sb.AppendLine("{");
sb.AppendLine($" public partial class {className} : INotifyPropertyChanged");
sb.AppendLine(" {");
// 生成INotifyPropertyChanged实现
sb.AppendLine(" public event PropertyChangedEventHandler PropertyChanged;");
sb.AppendLine();
sb.AppendLine(" protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)");
sb.AppendLine(" {");
sb.AppendLine(" PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));");
sb.AppendLine(" }");
sb.AppendLine();
// 为每个字段生成属性
foreach (var field in fields)
{
var fieldName = field.Identifier.ValueText;
var propertyName = ToPascalCase(fieldName.TrimStart('_'));
sb.AppendLine($" public {GetFieldType(field)} {propertyName}");
sb.AppendLine(" {");
sb.AppendLine($" get => {fieldName};");
sb.AppendLine(" set");
sb.AppendLine(" {");
sb.AppendLine($" if (!EqualityComparer<{GetFieldType(field)}>.Default.Equals({fieldName}, value))");
sb.AppendLine(" {");
sb.AppendLine($" {fieldName} = value;");
sb.AppendLine($" OnPropertyChanged();");
sb.AppendLine(" }");
sb.AppendLine(" }");
sb.AppendLine(" }");
sb.AppendLine();
}
sb.AppendLine(" }");
sb.AppendLine("}");
return sb.ToString();
}
private string GetFieldType(VariableDeclaratorSyntax field)
{
var fieldDeclaration = field.Parent.Parent as FieldDeclarationSyntax;
return fieldDeclaration?.Declaration.Type.ToString() ?? "object";
}
private string ToPascalCase(string input)
{
if (string.IsNullOrEmpty(input)) return input;
return char.ToUpper(input[0]) + input.Substring(1);
}
}
// 语法接收器:收集需要处理的类
public class SyntaxReceiver : ISyntaxReceiver
{
public List<ClassDeclarationSyntax> CandidateClasses { get; } = new List<ClassDeclarationSyntax>();
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
if (syntaxNode is ClassDeclarationSyntax classDeclaration
&& classDeclaration.AttributeLists.Count > 0)
{
CandidateClasses.Add(classDeclaration);
}
}
}
}
C#using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AppSourceGeneratorDemo
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field)]
public class AutoNotifyAttribute : Attribute
{
}
}
C#using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AppSourceGeneratorDemo
{
[AutoNotify]
public partial class UserViewModel
{
[AutoNotify]
private string _userName = "";
[AutoNotify]
private int _age;
[AutoNotify]
private bool _isActive;
}
}
C#namespace AppSourceGeneratorDemo
{
internal class Program
{
static void Main(string[] args)
{
var user = new UserViewModel();
// 测试生成的属性
user.PropertyChanged += (sender, e) =>
Console.WriteLine($"Property {e.PropertyName} changed!");
user.UserName = "张三";
user.Age = 25;
user.IsActive = true;
Console.WriteLine($"用户:{user.UserName}, 年龄:{user.Age}, 激活:{user.IsActive}");
}
}
}
XML<!-- 错误:使用.NET 5+ -->
<TargetFramework>net6.0</TargetFramework>
<!-- 正确:使用netstandard2.0确保兼容性 -->
<TargetFramework>netstandard2.0</TargetFramework>
XML<!-- 消费端项目必须这样引用 -->
<ItemGroup>
<ProjectReference Include="../SourceGeneratorDemo/SourceGeneratorDemo.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>
C#// 在Generator中添加调试支持
public void Execute(GeneratorExecutionContext context)
{
#if DEBUG
if (!System.Diagnostics.Debugger.IsAttached)
{
System.Diagnostics.Debugger.Launch();
}
#endif
// 生成逻辑...
}
通过本文的深入剖析,我们掌握了Source Generators的三个核心要点:
Source Generators不仅是一项技术革新,更是.NET生态向编译时优化演进的重要里程碑。它让我们能够在保持代码简洁的同时,获得接近手工优化的性能表现。
掌握这项技术,你将在团队中脱颖而出,成为那个"让代码自己写代码"的技术专家!
💡 思考题:
🔥 欢迎在评论区分享你的实战经验和遇到的挑战!觉得有用请转发给更多同行,让我们一起推动.NET技术的发展!
想了解更多C#高级技巧?关注我,持续分享实战干货!
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!