使用 roslyn 的 Source Generator 自动完成依赖收集和注册
在 使用 Hosting 构建 WPF 程序 提到,因为不使用 Stylet 默认的 IOC 容器,所以不能自动收集和注册 View/ViewModel,需要动手处理。
如果项目比较大,手动处理显然过于麻烦。这里使用 roslyn 的 Source Generator 自动完成依赖收集和注册。
源码 JasonGrass/WpfAppTemplate1: WPF + Stylet + Hosting
新建分析器项目
以类库的模板,新建 WpfAppTemplate1.Generators,或者直接使用 Rider 新建。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<IsRoslynComponent>true</IsRoslynComponent>
<RootNamespace>WpfAppTemplate1.Generators</RootNamespace>
<PackageId>WpfAppTemplate1.Generators</PackageId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.0"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.3.0"/>
</ItemGroup>
</Project>
编写 SourceGenerator 代码
新建一个类,继承自 ISourceGenerator
,并添加 Generator
Attribute。
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace WpfAppTemplate1.Generators;
[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.Screen
if (
clsNode.ClassDeclaration.Identifier.Text.EndsWith("ViewModel")
&& InheritsFrom(clsNode.ModelSymbol, "Stylet.Screen")
)
{
sourceBuilder.AppendLine($" services.AddSingleton<{fullName}>();");
}
// View 必须继承 System.Windows.FrameworkElement
else 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.Generators\WpfAppTemplate1.Generators.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;或者直接使用 Rider。
2 调试 source generator
对于新建的 source generator 项目,rider 会自动生成 launchSettings.json
,可以直接启动项目进行调试
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"DebugRoslynSourceGenerator": {
"commandName": "DebugRoslynComponent",
"targetProject": "../WpfAppTemplate1/WpfAppTemplate1.csproj"
}
}
}
参考
SamplesInPractice/SourceGeneratorSample at main · WeihanLi/SamplesInPractice
作者:
J.晒太阳的猫
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。