在 使用 Hosting 构建 WPF 程序 提到,因为不使用 Stylet 默认的 IOC 容器,所以不能自动收集和注册 View/ViewModel,需要动手处理。
如果项目比较大,手动处理显然过于麻烦。这里使用 roslyn 的 Source Generator 自动完成依赖收集和注册。
源码 JasonGrass/WpfAppTemplate1: WPF + Stylet + Hosting
新建分析器项目
以类库的模板,新建 WpfAppTemplate1.Analyzers,修改 TargetFramework
为 netstandard2.0
,添加 IsRoslynComponent
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><TargetFramework>netstandard2.0</TargetFramework><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable><LangVersion>12.0</LangVersion><IsRoslynComponent>true</IsRoslynComponent><EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules><AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath></PropertyGroup><ItemGroup><PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" PrivateAssets="all" /></ItemGroup></Project>
编写 SourceGenerator 代码
新建一个类,继承自 ISourceGenerator
,并添加 Generator
Attribute。
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;namespace WpfAppTemplate1.Analyzers.SourceGenerators;[Generator]
public class ViewDependencyInjectionGenerator : ISourceGenerator
{public void Initialize(GeneratorInitializationContext context) { }public void Execute(GeneratorExecutionContext context){// System.Diagnostics.Debugger.Launch();// 获取所有语法树var compilation = context.Compilation;var syntaxTrees = context.Compilation.SyntaxTrees;// 查找目标类型(ViewModel和View)var clsNodeList = syntaxTrees.SelectMany(tree => tree.GetRoot().DescendantNodes()).OfType<ClassDeclarationSyntax>().Where(cls =>cls.Identifier.Text.EndsWith("ViewModel") || cls.Identifier.Text.EndsWith("View")).Select(cls => new{ClassDeclaration = cls,ModelSymbol = compilation.GetSemanticModel(cls.SyntaxTree).GetDeclaredSymbol(cls),}).ToList();// 生成注册代码var sourceBuilder = new StringBuilder(@"
using Microsoft.Extensions.DependencyInjection;public static class ViewModelDependencyInjection
{public static void AddViewModelServices(this IServiceCollection services){
");HashSet<string> added = new HashSet<string>();foreach (var clsNode in clsNodeList){if (clsNode.ModelSymbol == null){continue;}// var namespaceName = type.ModelSymbol.ContainingNamespace.ToDisplayString();var fullName = clsNode.ModelSymbol.ToDisplayString(); // 包含命名空间的全称if (!added.Add(fullName)){// 避免因为 partial class 造成的重复添加continue;}// ViewModel 必须继承 Stylet.Screenif (clsNode.ClassDeclaration.Identifier.Text.EndsWith("ViewModel")&& InheritsFrom(clsNode.ModelSymbol, "Stylet.Screen")){sourceBuilder.AppendLine($" services.AddSingleton<{fullName}>();");}// View 必须继承 System.Windows.FrameworkElementelse if (clsNode.ClassDeclaration.Identifier.Text.EndsWith("View")&& InheritsFrom(clsNode.ModelSymbol, "System.Windows.FrameworkElement")){sourceBuilder.AppendLine($" services.AddSingleton<{fullName}>();");}}sourceBuilder.AppendLine(" }");sourceBuilder.AppendLine("}");var code = sourceBuilder.ToString();// 添加生成的代码到编译过程context.AddSource("ViewModelDependencyInjection.g.cs",SourceText.From(code, Encoding.UTF8));}private bool InheritsFrom(INamedTypeSymbol typeSymbol, string baseClassName){while (typeSymbol.BaseType != null){if (typeSymbol.BaseType.ToDisplayString() == baseClassName){return true;}typeSymbol = typeSymbol.BaseType;}return false;}
}
最终生成的代码如下:
using Microsoft.Extensions.DependencyInjection;public static class ViewModelDependencyInjection
{public static void AddViewModelServices(this IServiceCollection services){services.AddSingleton<WpfAppTemplate1.View.RootView>();services.AddSingleton<WpfAppTemplate1.ViewModel.RootViewModel>();}
}
这里没有指定命名空间,直接使用默认的命名空间。
在 WpfAppTemplate1 项目中使用
这里没有生成 nuget 包,直接使用项目引用
<ItemGroup><ProjectReference Include="..\WpfAppTemplate1.Analyzers\WpfAppTemplate1.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /></ItemGroup>
OutputItemType="Analyzer"
表示将项目添加为分析器
ReferenceOutputAssembly="false"
表示此项目无需引用分析器项目的程序集
然后,在 Bootstrapper
中调用
protected override void ConfigureIoC(IServiceCollection services)
{base.ConfigureIoC(services);// services.AddSingleton<RootViewModel>();// services.AddSingleton<RootView>();services.AddViewModelServices();
}
至此,大功告成。
可以在这里找到自动生成的代码
几个问题
1 编写完成之后没有生效
VS 对代码生成器的支持看起来还不是很好,尝试重启 VS
2 调试 source generator
使用 System.Diagnostics.Debugger.Launch();
来调试代码生成器中的代码,在运行时,会询问是否加载调试器
参考
SamplesInPractice/SourceGeneratorSample at main · WeihanLi/SamplesInPractice
使用 Source Generator 在编译你的 .NET 项目时自动生成代码 - walterlv
.net - C# Source Generator - warning CS8032: An instance of analyzer cannot be created - Stack Overflow
原文链接:https://www.cnblogs.com/jasongrass/p/18540540