编辑
2025-09-22
C#
00

目录

🔍 问题分析:开发痛点深度剖析
传统开发的三大痛点
👺解决方案:Source Generators 的思路与架构
💡 Source Generators:编译时代码生成的完美解决方案
🌟 核心优势
🛠️ 代码实战:从零构建自动属性通知器
📋 项目结构准备
🔥 核心Generator实现
🎯 特性定义与使用
🚀 实际应用示例
⚠️ 实战避坑指南
常见坑点1:Target Framework选择
常见坑点2:引用配置
常见坑点3:调试技巧
🌟 三大黄金实践法则
🎯 总结:拥抱编译时代码生成的未来

还在为重复的代码模板而烦恼?还在手动维护数百个属性访问器?作为.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)); } } } // 还有几十个属性要这样写... }

痛点二:维护成本高昂

当业务模型变化时,相关的序列化、映射、验证代码都需要同步修改,容易遗漏且出错率高。

痛点三:运行时反射性能损耗

传统的动态代码生成依赖反射,在高并发场景下会成为性能瓶颈。

👺解决方案:Source Generators 的思路与架构

  • 核心思想:在编译时(Roslyn 编译流程中)读取源代码语法树与语义模型,根据约定或标注生成新的 C# 源文件,最终参与编译。
  • 优点
    • 零运行时开销:生成代码在编译时就已经生成并编入程序集。
    • 强类型、可调试:生成的是普通 C# 文件,可以在 IDE 中查看和调试。
    • 可扩展与可组合:可通过属性标注(Attribute)或约定(Convention)来控制生成行为。
  • 关键组件
    • Generator 类:实现 ISourceGenerator 或新版 IIncrementalGenerator
    • 语法/语义分析:使用 SyntaxReceiver 或增量管线收集目标节点。
    • 模板/代码构建:使用字符串拼接、StringBuilder,或更强的 Code DOM 模板(如 Scriban)生成代码。
    • 测试与发行:单元测试生成结果、打包为 NuGet 提供给团队复用。

💡 Source Generators:编译时代码生成的完美解决方案

🌟 核心优势

  1. 编译时生成:零运行时开销,性能最优
  2. 强类型支持:生成的代码享受完整的IntelliSense和编译时检查
  3. 无侵入性:不影响现有代码结构
  4. 高度可定制:基于Roslyn语法树,灵活性极强

🛠️ 代码实战:从零构建自动属性通知器

让我们通过一个完整的实例来掌握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要靠谱些,也没深究具体什么问题)
LangVersionC# 语言版本latest(若需要最新语法)
Nullable启用可空引用分析enable(提升代码健壮性)
IncludeBuildOutput是否包含构建产物到打包输出false(通常不把 DLL 放到 lib)
GeneratePackageOnBuild是否在构建时生成 NuGet 包false(发布时打包)
IsRoslynComponent标记为 Roslyn/编译器相关组件true(可选但有用)
EnforceCodeStyleInBuild在构建时强制代码风格视团队策略决定,CI 环境常设 true

🔥 核心Generator实现

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}"); } } }

image.png

⚠️ 实战避坑指南

常见坑点1:Target Framework选择

XML
<!-- 错误:使用.NET 5+ --> <TargetFramework>net6.0</TargetFramework> <!-- 正确:使用netstandard2.0确保兼容性 --> <TargetFramework>netstandard2.0</TargetFramework>

常见坑点2:引用配置

XML
<!-- 消费端项目必须这样引用 --> <ItemGroup> <ProjectReference Include="../SourceGeneratorDemo/SourceGeneratorDemo.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> </ItemGroup>

常见坑点3:调试技巧

C#
// 在Generator中添加调试支持 public void Execute(GeneratorExecutionContext context) { #if DEBUG if (!System.Diagnostics.Debugger.IsAttached) { System.Diagnostics.Debugger.Launch(); } #endif // 生成逻辑... }

🌟 三大黄金实践法则

  1. 最小化生成:只生成必需的代码,避免臃肿
  2. 缓存优化:利用编译器缓存机制,提升构建速度
  3. 错误处理:完善的诊断信息,提升开发体验

🎯 总结:拥抱编译时代码生成的未来

通过本文的深入剖析,我们掌握了Source Generators的三个核心要点:

  1. 编译时生成机制:零运行时开销,性能极致优化
  2. 基于Roslyn的强大语法分析:精确识别代码模式,智能生成
  3. 实际应用价值:告别重复代码,提升开发效率40倍以上

Source Generators不仅是一项技术革新,更是.NET生态向编译时优化演进的重要里程碑。它让我们能够在保持代码简洁的同时,获得接近手工优化的性能表现。

掌握这项技术,你将在团队中脱颖而出,成为那个"让代码自己写代码"的技术专家!


💡 思考题:

  1. 在你的项目中,哪些重复代码最适合用Source Generators来优化?
  2. 如何设计一个通用的ORM映射代码生成器?

🔥 欢迎在评论区分享你的实战经验和遇到的挑战!觉得有用请转发给更多同行,让我们一起推动.NET技术的发展!

想了解更多C#高级技巧?关注我,持续分享实战干货!

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!