如何用Source Generator实现StrongViewLocator简化注册方法重载
用Source Generator简化视图-视图模型无反射注册语法
现有需手动编写的无反射注册代码:
ViewLocator = new StrongViewLocator() .Register<MainWindowViewModel>(new ViewDefinition(typeof(MainWindow), () => new MainWindow())) .Register<AddTextDialogViewModel>(new ViewDefinition(typeof(AddTextDialog), () => new AddTextDialog()));
期望简化为更简洁的泛型链式调用:
ViewLocator = new StrongViewLocator() .Register<MainWindowViewModel, MainWindow>() .Register<AddTextDialogViewModel, AddTextDialog>();
以下是通过Source Generator实现该需求的具体步骤:
1. 定义泛型扩展方法占位
在主项目中定义一个空的泛型扩展方法,用于让编译器识别简化后的语法,实际逻辑由Source Generator生成:
namespace YourAppNamespace; public static partial class StrongViewLocatorExtensions { // 占位方法,仅提供语法提示,编译时会被生成代码替换 public static StrongViewLocator Register<TVm, TView>(this StrongViewLocator locator) { throw new NotImplementedException("实际实现由Source Generator生成"); } }
2. 编写Source Generator
创建一个.NET Standard类库项目,引用Microsoft.CodeAnalysis.CSharp和Microsoft.CodeAnalysis.Analyzers NuGet包,实现ISourceGenerator接口:
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Collections.Generic; using System.Text; namespace ViewLocatorGenerator; [Generator] public class ViewLocatorRegistrationGenerator : ISourceGenerator { public void Initialize(GeneratorInitializationContext context) { // 注册语法接收器,筛选目标泛型方法调用 context.RegisterForSyntaxNotifications(() => new ViewLocatorSyntaxReceiver()); } public void Execute(GeneratorExecutionContext context) { if (context.SyntaxReceiver is not ViewLocatorSyntaxReceiver receiver) return; var semanticModel = context.Compilation; foreach (var invocation in receiver.MatchingInvocations) { // 解析语义信息,验证是否为StrongViewLocator的Register调用 if (semanticModel.GetSemanticModel(invocation.SyntaxTree).GetSymbolInfo(invocation.Expression).Symbol is not IMethodSymbol methodSymbol) continue; if (methodSymbol.ReceiverType?.Name != "StrongViewLocator") continue; // 获取泛型参数(视图模型、视图类型) var typeArgs = methodSymbol.TypeArguments; if (typeArgs.Length != 2) continue; var vmTypeFullName = typeArgs[0].ToDisplayString(); var viewTypeFullName = typeArgs[1].ToDisplayString(); // 获取调用所在的命名空间 var nsDeclaration = invocation.Ancestors().OfType<NamespaceDeclarationSyntax>().FirstOrDefault(); var targetNamespace = nsDeclaration?.Name.ToString() ?? "YourAppNamespace"; // 生成对应的扩展方法实现代码 var sourceCode = new StringBuilder(); sourceCode.Append($@" namespace {targetNamespace}; public static partial class StrongViewLocatorExtensions {{ public static StrongViewLocator Register<{typeArgs[0].Name}, {typeArgs[1].Name}>(this StrongViewLocator locator) {{ return locator.Register<{vmTypeFullName}>(new ViewDefinition(typeof({viewTypeFullName}), () => new {viewTypeFullName}())); }} }}"); // 将生成代码添加到编译上下文 context.AddSource($"ViewLocator_{typeArgs[0].Name}_{typeArgs[1].Name}.g.cs", sourceCode.ToString()); } } // 语法接收器:筛选Register<,>()格式的泛型方法调用 private class ViewLocatorSyntaxReceiver : ISyntaxReceiver { public List<InvocationExpressionSyntax> MatchingInvocations { get; } = new(); public void OnVisitSyntaxNode(SyntaxNode syntaxNode) { if (syntaxNode is InvocationExpressionSyntax invocation && invocation.Expression is MemberAccessExpressionSyntax memberAccess && memberAccess.Name.Identifier.Text == "Register" && invocation.TypeArgumentList?.Arguments.Count == 2) { MatchingInvocations.Add(invocation); } } } }
3. 配置生成器并验证
- 将生成器项目引用到主项目中,修改主项目的csproj文件,添加以下引用配置:
<ProjectReference Include="..\ViewLocatorGenerator\ViewLocatorGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> - 编译主项目时,Source Generator会自动为每个
Register<VM, V>()调用生成对应的扩展方法实现,编译后生成的代码会替换占位方法逻辑,最终运行效果与原始手动注册代码完全一致。
内容的提问来源于stack exchange,提问作者Etienne Charland
相关产品推荐
相关产品推荐

