首页 > 其他分享 >使用 roslyn 的 Source Generator 自动完成依赖收集和注册

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

时间:2024-11-11 20:42:44浏览次数:1  
标签:Generator roslyn var Source clsNode services using cls

使用 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.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();
}

至此,大功告成。

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

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

标签:Generator,roslyn,var,Source,clsNode,services,using,cls
From: https://www.cnblogs.com/jasongrass/p/18540540

相关文章

  • gcc 1.c和g++ 1.c编译阶段有什么区别?如何知道g++编译默认会定义_GNU_SOURCE?
    gcc1.c和g++1.c编译阶段有什么区别?借用 gcc1.c和g++1.c有什么区别? 的示例代码,以汇编代码为比较目标,再经过汇编,最后生成ELF文件,三个过程结果均无差异,这个阶段充分证明了c和c++是多么相似。编译到汇编gcc-S1.c-o1.sg++-S1.c-o11.s .file "1.c"......
  • 使用Roslyn的源生成器生成DTO
    前言源生成器的好处很多,通过在编译时生成代码,可以减少运行时的反射和动态代码生成,从而提高应用程序的性能,有时候需要对程序AOT以及裁剪编译的dll也是需要用SG来处理的。我们开发程序应该都绕不过Mapper对象映射,用的比较多的库可能就是AutoMapper,Maspter之内的三方库吧;这......
  • 利用FreeSql.Generator自动根据数据库表动态生成实体类
    安装dotnettoolinstall-gFreeSql.Generator示例FreeSql.Generator-Razor1-NameOptions0,0,0,1-NameSpaceLinCms.Core.Entities-DB"MySql,DataSource=127.0.0.1;Port=3306;UserID=root;Password=123456;InitialCatalog=lincms;Charset=utf8;SslMode=none;M......
  • js 的generator函数是什么
    在JavaScript中,Generator函数(生成器函数)是一种特殊类型的函数,它可以暂停执行并且可以在后续的某个时刻恢复执行。与普通函数不同,Generator函数不会在调用时立即执行,而是返回一个Generator对象,你可以通过该对象控制函数的执行过程。1.如何定义一个Generator函数Generato......
  • Failed to load local image resource(在小程序中,`src` 属性需要使用双花括号 `{{ }}`
    文章目录修改WXML文件确保图像文件路径正确检查逻辑层代码总结[渲染层网络层错误]Failedtoloadlocalimageresource/components/antiFakeQuery/imageSrctheserverrespondedwithastatusof500(HTTP/1.1500InternalServerError)(env:Windows......
  • Java 中的 try-with-resources 详解
    在Java7之前,处理资源关闭通常使用try-catch-finally块。虽然这种方式可以确保资源被正确关闭,但代码显得冗长且容易出错。Java7引入了try-with-resources语法,使得资源管理变得更加简洁和安全。本文将详细介绍try-with-resources的使用方法和优势。1try-catch-fi......
  • 学习笔记(二十六):资源分类与访问(Resources)
    概述:应用开发中使用的各类资源文件,需要放入特定子目录中存储管理。资源目录的示例如下所示,base目录、限定词目录、rawfile目录、resfile目录称为资源目录;element、media、profile称为资源组目录。resources|---base||---element|||---string.json||---media......
  • YUM源服务器搭建之详解(Detailed Explanation of Building a YUM Source Server)
      ......
  • start-all.sh脚本启动Hadoop的NameNode、DataNode、ResourceManager和NodeManager失败
    今天在做大数据实验时,在终端,start-all.sh脚本启动Hadoop的NameNode、DataNode、ResourceManager和NodeManager失败,出现下面的错误信息:[root@node1hadoop]#./sbin/start-all.shStartingnamenodeson[node1]ERROR:AttemptingtooperateonhdfsnamenodeasrootERROR:butt......
  • WPF ItemsSource referenced StaticResource
    //xaml<Window.Resources><local:SizeConverterx:Key="sizeConverter"/><local:BooksDatax:Key="booksData"/></Window.Resources><Grid><DataGridGrid.Row="1"......