我本来了解Source Generators源生成器但是不知道什么地方用,今天我正好有个需求,从
切面编程
用到了Source Generators
;这里作为一个典型场景记录
任何代码的封装都是犯懒的结果,今天升级框架的时候要给基础表增加数据变更全局通知的方法,直接通知到前端,这个数据变了刷新一下
本来前端我会这样写 .Sys2DDL()
就是指定这个下拉控件是一个标准部门控件,应该加载部门数据;这时我可以在
Sys2DDL
中绑定数据的同时,注册一个事件,就是等变更的时候重新刷新数据;这样的话就是全局的所有包括Sys2DDL
的控件都刷新了不用每个页面自己写;
需求就是这,然后拆解,第一步,监听数据变化,标准方式是这样的
//部门表持久化 public partial class BLLSystem_2 : SqlSugarHelp<Mssystem_2>, ISqlQueryed<Mssystem_2> { ... //实现ISqlQueryed public async Task<MsReturned> Handle(SqlSugarQueryed_Event<Mssystem_2> request, CancellationToken cancellationToken) { var type = request.type; if (type != SqlSugarQueryedType.Query) { SignalRHelper.SendHubMessage("updateMssystem_2"); } return new MsReturned(); } }
BLLSystem_2
类要继承ISqlQueryed<T>
然后实现Handle
就可以了,问题来了,这样的基础表有多个(超过3我就赖的写了)明显是个重复的方法,如果我能写成特性
直接放类上,这样就可以给别的类用了,我的目标是这样
[WathingAndSend] // 或者 [WathingAndSend("MyCustomEvent")] public partial class BLLSystem_2 : SqlSugarHelp<Mssystem_2> { // 内部空空如也,或者只关心它自己的核心逻辑 }
首先[WathingAndSend]
没有破坏类的原始结构,如何让一个类,动态的继承一个接口并实现呢,这属于套了两层AOP,需要再切一下,这时要用到Source Generators源生成器
在编译时动态生成类
//动态生成的 public partial class BLLSystem_2 : ISqlQueryed<Mssystem_2> { public async Task<MsReturned> Handle... }
流程就是:找到WathingAndSend
标记 → 拿到命名空间
→ 拿到<T>
→ 拼代码 → 注入编译;
剩下的交给AI
(懒)🛋️
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System.Collections.Generic; using System.Linq; using System.Text; namespace HD_System.Generator { /// <summary> /// 一个 Roslyn Source Generator,用于自动实现数据变更后的 SignalR 通知逻辑。 /// 它会查找所有标记了 [WathingAndSend] 特性的类,并为它们自动生成 <c>ISqlQueryed<T></c> 接口的实现。 /// 主要功能: /// 1. 自动实现 Handle 方法。 /// 2. 在非查询操作后,通过 SignalR 发送通知。 /// 3. 支持通过特性参数自定义 SignalR 的事件名称。 /// 4. 提供编译时错误检查,强制要求目标类必须是 'partial'。 /// </summary> [Generator] public class WathingAndSendGenerator : ISourceGenerator { /// <summary> /// 定义一个诊断规则,用于在目标类未标记为 'partial' 时报告一个清晰的编译错误。 /// 这是为了提供比默认编译器错误(CS0260)更友好的用户体验。 /// </summary> private static readonly DiagnosticDescriptor MustBePartialRule = new DiagnosticDescriptor( id: "WATG001", // 自定义错误码 title: "带有[WathingAndSend]的类必须是 partial 的", messageFormat: "类 '{0}' 使用了 [WathingAndSend] 特性,但没有被标记为 'partial'。", category: "Usage", // 错误类别 defaultSeverity: DiagnosticSeverity.Error, // 严重级别:错误,会导致编译失败 isEnabledByDefault: true, description: "Source Generator 需要为该类添加另一部分代码,因此该类必须声明为 partial。"); /// <summary> /// 初始化 Generator。此方法在编译开始时由 Roslyn 调用一次。 /// 我们在此注册一个语法接收器(SyntaxReceiver),它能高效地筛选出我们可能感兴趣的语法节点(即带有特性的类), /// 以便在 Execute 方法中进行进一步的语义分析。 /// </summary> public void Initialize(GeneratorInitializationContext context) { context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); } /// <summary> /// 执行代码生成的核心方法。此方法在编译过程中被调用。 /// 它会分析由 SyntaxReceiver 收集到的候选类,并为符合条件的类生成代码。 /// </summary> public void Execute(GeneratorExecutionContext context) { if (!(context.SyntaxReceiver is SyntaxReceiver receiver)) return; var attributeSymbol = context.Compilation.GetTypeByMetadataName(typeof(WathingAndSendAttribute).FullName); foreach (var classDeclaration in receiver.CandidateClasses) { var model = context.Compilation.GetSemanticModel(classDeclaration.SyntaxTree); var classSymbol = model.GetDeclaredSymbol(classDeclaration); var attributeData = classSymbol.GetAttributes().FirstOrDefault(ad => ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default)); if (attributeData != null) { if (!classDeclaration.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))) { var diagnostic = Diagnostic.Create(MustBePartialRule, classDeclaration.GetLocation(), classSymbol.Name); context.ReportDiagnostic(diagnostic); continue; } string? customMessage = null; if (attributeData.ConstructorArguments.Length > 0) { var arg = attributeData.ConstructorArguments[0]; if (!arg.IsNull && arg.Value is string value) { customMessage = value; } } var baseType = classSymbol.BaseType; if (baseType == null || !baseType.Name.Contains("SqlSugarHelp") || baseType.TypeArguments.Length == 0) continue; var modelTypeSymbol = baseType.TypeArguments[0]; string modelTypeName = modelTypeSymbol.Name; string hubMessageToSend = string.IsNullOrEmpty(customMessage) ? $"update{modelTypeName}" : customMessage; string namespaceName = classSymbol.ContainingNamespace.ToDisplayString(); string className = classSymbol.Name; var source = GeneratePartialClass(namespaceName, className, modelTypeName, hubMessageToSend); context.AddSource($"{className}_Generated.cs", SourceText.From(source, Encoding.UTF8)); } } } /// <summary> /// 根据提供的参数,生成 partial 类的 C# 代码字符串。 /// </summary> /// <param name="namespaceName">目标类所在的命名空间。</param> /// <param name="className">目标类的名称。</param> /// <param name="modelTypeName">从 <c>SqlSugarHelp<T></c> 中提取的泛型模型 T 的名称。</param> /// <param name="hubMessage">要发送的 SignalR Hub 消息事件名。</param> /// <returns>包含生成的 partial class 代码的字符串。</returns> private string GeneratePartialClass(string namespaceName, string className, string modelTypeName, string hubMessage) { return $$""" // <auto-generated/> // 此文件由 WathingAndSendGenerator 自动生成,请勿手动修改。 #nullable enable using HD.Commons.DataBase; using HD.ModelBase; using HD_System.Models; using Microsoft.AspNetCore.SignalR; using System.Threading; using System.Threading.Tasks; namespace {{namespaceName}} { /// <summary> /// 这是由 Source Generator 自动生成的 {{className}} 的部分类。 /// 它实现了 <c>ISqlQueryed<{{modelTypeName}}></c> 接口,并处理了数据变更后的 SignalR 通知。 /// </summary> public partial class {{className}} : ISqlQueryed<{{modelTypeName}}> { public async Task<MsReturned> Handle(SqlSugarQueryed_Event<{{modelTypeName}}> request, CancellationToken cancellationToken) { var type = request.type; if (type != SqlSugarQueryedType.Query) { // 在非查询操作后发送 SignalR 消息 SignalRHelper.SendHubMessage("{{hubMessage}}"); } // 返回一个默认的成功结果 return await Task.FromResult(new MsReturned()); } } } """; } /// <summary> /// 一个语法接收器,用于在编译的早期阶段快速、轻量地收集所有可能需要处理的类声明。 /// 它只进行语法层面的筛选(例如,检查是否存在特性),而不进行昂贵的语义分析。 /// </summary> private class SyntaxReceiver : ISyntaxReceiver { /// <summary> /// 存储所有带有至少一个特性的类声明节点。 /// </summary> public List<ClassDeclarationSyntax> CandidateClasses { get; } = new List<ClassDeclarationSyntax>(); /// <summary> /// 当 Roslyn 遍历语法树时,每个节点都会调用此方法。 /// </summary> /// <param name="syntaxNode">当前访问的语法节点。</param> public void OnVisitSyntaxNode(SyntaxNode syntaxNode) { if (syntaxNode is ClassDeclarationSyntax classDeclarationSyntax && classDeclarationSyntax.AttributeLists.Count > 0) { CandidateClasses.Add(classDeclarationSyntax); } } } } }
本文作者:没想好
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!