在 使用 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.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.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
原文链接:https://www.cnblogs.com/jasongrass/p/18540540
标签:Generator,roslyn,var,Source,clsNode,services,using,cls From: https://www.cnblogs.com/jasongrass/p/18540540