使用 roslyn 的 Source Generator 自动完成依赖收集和注册

news/2024/11/14 11:57:25/文章来源:https://www.cnblogs.com/jasongrass/p/18540540

在 使用 Hosting 构建 WPF 程序 提到,因为不使用 Stylet 默认的 IOC 容器,所以不能自动收集和注册 View/ViewModel,需要动手处理。

如果项目比较大,手动处理显然过于麻烦。这里使用 roslyn 的 Source Generator 自动完成依赖收集和注册。

源码 JasonGrass/WpfAppTemplate1: WPF + Stylet + Hosting

新建分析器项目

以类库的模板,新建 WpfAppTemplate1.Analyzers,修改 TargetFrameworknetstandard2.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();
}

至此,大功告成。

可以在这里找到自动生成的代码

image

几个问题

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/831980.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

高级语言程序设计第七次作业

这个作业属于哪个课程:https://edu.cnblogs.com/campus/fzu/2024C/ 这个作业要求在哪里: https://edu.cnblogs.com/campus/fzu/2024C/homework/13304 学号:102400204 姓名:刘嘉奕 1.2.需要思维贯穿,逻辑性强3.刚开始没用指针,导致结果为负数,但在使用指针过程中还存在不…

叉积

向量基本运算点积叉积一.Transmitters由叉积的基本定理得出,要判断一个点c再一条直线ab的左边还是右边,只要看(b-a)*(c-a)大于0还是小于0,对于这题,我们可以将左右边看成发射范围的半圆,因为要找出覆盖最多点的数量,所以最优解肯定有一个点再直径上,再通过叉积找出所…

内核脏牛提权

脏牛靶机 已知靶机账号密码:msfadmin/msfadmin 靶机开启ssh服务开启靶机获取IP msfadmin/msfadmin登录 ifconfig #获取靶机IP得到靶机IP:192.168.213.247 开启kali ssh链接靶机 直接输入ssh msfadmin@192.168.213.247进行链接会出现报错 出现报错的原因是ssh客户端和服务器之…

[极客大挑战 2019]Secret

首页什么都没有,查看网页源码,发现有一个链接,提示found除去view-source访问,得到以下页面,点击secret直接查阅结束,没回显flag,依旧在卖关子。使用burp suite抓包获取返回的源码,找到注释中有个文件secr3t.php访问secr3t.php得到代码里说flag在flag.php里,直接访问fl…

今年测试这工资是认真的吗?

在如今的求职市场中,软件测试面试中的“八股文”几乎成为了一种必考内容。所谓“八股文”,指的是一系列标准化的技术面试题目,这些题目涵盖了测试理论、接口自动化测试、测试工具框架、性能测试、项目场景等多个方面...📝 博主首页 : 「码上生花」 ,同名公众号 :「伤心的…

开发人员,千万不要去碰那该死的业务参数,无论什么时候!

你好呀,我是歪歪。 前几天发了一个牢骚:本来只是单纯的吐槽一下,但是好多人对其中的细节比较感兴趣。 大家都是搞技术的嘛,对于“踩 BUG”这种喜闻乐见的事情,有兴趣是很正常的。 其实我这个 BUG,其实严格意义上不能叫做 BUG,因为和程序无关,甚至和技术的关系都不算大。…

java小课设:使用MySQL做一个聊天室

bro是个懒狗,耗时一个晚上,只写了一些基础功能,其他的可以根据需要自己添加实现思路:在MySQL数据库中设置一个message表,用来存储聊天信息,聊天界面输入的内容写入message表,用户程序每秒从MySQL中获取一次聊天记录,并加载进入自己的页面,实现聊天室。食用方法: Chat…

Quartz集群增强版_00.How to use?(如何使用)

Quartz集群增强版_00.How to use?(如何使用)转载请著名出处 https://www.cnblogs.com/funnyzpc/p/18540378开源地址 https://github.com/funnyzpc/quartz 表的基本结构 总的来说任务的配置及开发基本遵从上图的表的基本关系,除 app 以及 node 之外均需要手动手动配置,app…

开源三代示波器的高速波形刷新方案开源,支持VNC远程桌面,手机,Pad,电脑均可访问(2024-11-11)

说明: 1、本来这段时间是一年一度Hackaday硬件设计开源盛宴,但hackaday电子大赛在去年终结了。所以我开源个我的吧。 2、三代示波器的高速波形刷新方案,前两年就做好了,这两年忙H7-TOOL的更新比较多,三代示波器的更新就搁置了。但刷新方案是没问题的,开源分享给大家。 3、…

PSQL 环境安装配置

准备工作:安装包 plsqldev1400x64.msi 注册码 汉化包 chinese.exe 轻量级数据 instantclient_11_2 安装【PSQL】第一步大法操作! 默认的安装路径:C:\Program Files\PLSQL Developer 14安装【轻量级 instantclient_11_2】 复制或解压到 C:\Program Files\PLSQL Developer…

零声QT学习 一

int main(int argc, char *argv[]) {QApplication a(argc, argv);//QApplication a(argc, argv),针对QWidget应用程序,管理和设置Qt程序的运行//QGuiApplication a(argc, argv),针对非QWidget应用程序,如QQuick//QcoreApplication a(argc, argv),针对无界面的应用程序MainWindo…

【教程】第四章:任务与评论插件 —— 如虎添翼,顺利掌握

一起在 NocoBase 中创造精彩应用!这些教程将通过手把手的操作,帮助你全面掌握核心功能,激发灵感,打造并分享满足多样需求的应用。回顾上一节 小伙伴们还记得上一节的挑战任务吗?我们要为任务表配置 状态 和 附件 字段,并在任务列表里展示它们。别急,咱们先揭晓答案!状态…