如何用IIncrementalGenerator查找目标项目实现指定接口的类以生成工厂类
在Blazor中使用IIncrementalGenerator生成实现IEditPresenter的工厂类
要在IIncrementalGenerator中找到所有实现IEditPresenter的类,不能再用旧的全量遍历语法树方式,而是要利用增量生成管道完成筛选、符号绑定和信息提取。以下是完整的修改实现:
完整修改代码
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System.Text; [Generator] public class MySourceGenerator : IIncrementalGenerator { public readonly record struct PresentationFactoryToGenerate { public readonly string Name; public readonly string IdType; public readonly string EditorType; public PresentationFactoryToGenerate(string name, string editorType, string idType) { Name = name; EditorType = editorType; IdType = idType; } } public void Initialize(IncrementalGeneratorInitializationContext context) { // 1. 筛选非抽象、非静态的类声明语法节点 var classDeclarations = context.SyntaxProvider .CreateSyntaxProvider( predicate: static (node, _) => node is ClassDeclarationSyntax classDecl && !classDecl.Modifiers.Any(SyntaxKind.AbstractKeyword) && !classDecl.Modifiers.Any(SyntaxKind.StaticKeyword), transform: static (ctx, _) => ctx.Node as ClassDeclarationSyntax) .Where(static classDecl => classDecl != null); // 2. 绑定语义符号,过滤出实现IEditPresenter`2的类 var presenterSymbols = classDeclarations .SelectMany(static (classDecl, cancellationToken) => { if (context.SemanticModel.GetDeclaredSymbol(classDecl, cancellationToken) is not INamedTypeSymbol classSymbol) return Enumerable.Empty<INamedTypeSymbol>(); // 替换为你实际的IEditPresenter命名空间 var editPresenterInterface = classSymbol.Compilation.GetTypeByMetadataName("Blazr.App.Presentation.IEditPresenter`2"); if (editPresenterInterface == null) return Enumerable.Empty<INamedTypeSymbol>(); // 检查类是否实现目标接口 var implementsInterface = classSymbol.AllInterfaces.Any(iface => iface.ConstructedFrom.Equals(editPresenterInterface, SymbolEqualityComparer.Default)); return implementsInterface ? new[] { classSymbol } : Enumerable.Empty<INamedTypeSymbol>(); }); // 3. 提取泛型参数和类名,转换为生成所需结构 var presentersToGenerate = presenterSymbols .Select(static (classSymbol, cancellationToken) => { var implementedInterface = classSymbol.AllInterfaces.First(iface => iface.ConstructedFrom.MetadataName == "IEditPresenter`2"); var genericArgs = implementedInterface.TypeArguments; string editorType = genericArgs[0].ToDisplayString(); string idType = genericArgs[1].ToDisplayString(); return new PresentationFactoryToGenerate(classSymbol.Name, editorType, idType); }); // 4. 注册增量输出,生成工厂类代码 context.RegisterSourceOutput(presentersToGenerate, static (spc, item) => { string factoryCode = BuildPresenterFactory(item); spc.AddSource( $"{item.Name}EditPresenterFactory.g.cs", SourceText.From(factoryCode, Encoding.UTF8)); }); } private string BuildPresenterFactory(PresentationFactoryToGenerate item) { // 修正硬编码的Presenter类型,使用动态传入的类名 return $$""" namespace Blazr.App.Presentation; public class {{item.Name}}EditPresenterFactory { private readonly IServiceProvider _serviceProvider; public {{item.Name}}EditPresenterFactory(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public async ValueTask<IEditPresenter<{{item.EditorType}}, {{item.IdType}}>> GetPresenterAsync({{item.IdType}} id) { var presenter = ActivatorUtilities.CreateInstance<{{item.Name}}>(_serviceProvider); ArgumentNullException.ThrowIfNull(presenter); await presenter.LoadAsync(id); return presenter; } } """; } }
关键要点说明
增量管道设计:
- 用
SyntaxProvider.CreateSyntaxProvider只筛选目标类,减少无效计算,符合增量生成的性能要求。 - 通过
SemanticModel.GetDeclaredSymbol绑定语法节点到语义符号,确保能准确获取类的接口实现信息。
- 用
泛型接口匹配:
- 泛型接口的元数据名称格式为
接口名+泛型参数数量,比如IEditPresenter<TEditor, TId>对应IEditPresenter2`。 - 使用
SymbolEqualityComparer.Default做符号比较,避免因命名空间或别名导致的匹配错误。
- 泛型接口的元数据名称格式为
输出逻辑修正:
- 用
RegisterSourceOutput替代旧的RegisterPostInitializationOutput,仅在目标类变化时重新生成对应工厂类,符合增量生成生命周期。 - 移除硬编码的
FarmEditPresenter,动态使用传入的类名,确保生成代码与目标Presenter类匹配。
- 用
内容的提问来源于stack exchange,提问作者MrC aka Shaun Curtis
相关产品推荐
相关产品推荐

