使用.NET源生成器(SG)实现一个自动注入的生成器

DI依赖注入对我们后端程序员来说肯定是基础中的基础了,我们经常会使用下面的代码注入相关的service

services.AddScoped();
services.AddTransient();
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddSingleton();
services.AddScoped();

对于上面的代码如果代码量很大 而且随着项目的迭代可能会堆积更多的代码,对于很多程序员来说第一想到的可能是透过反射批量注入,当然这也是最简单最直接的方式,今天我们使用源生成器的方式实现这个功能, 使用源生成器的方式好处还是有的 比如AOT需求,极致性能要求

实现这个功能的具体步骤:

定义Attribute-标注Attribute-遍历代码中标注Attribute的metadata集合-生成源代码

首先我们定义一个Attribute用于标注需要注入的类

namespace Biwen.AutoClassGen.Attributes
{
    using System;
    /// 
    /// 服务生命周期
    /// 
    public enum ServiceLifetime
 {
        Singleton = 1,
        Transient = 2,
        Scoped = 4,
    }

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
    public class AutoInjectAttribute : Attribute
    {
        public ServiceLifetime ServiceLifetime { get; set; }
        public Type BaseType { get; set; }
        /// 
        /// 
        /// 
        /// NULL表示服务自身
        /// 服务生命周期
        public AutoInjectAttribute(Type baseType = null, ServiceLifetime serviceLifetime = ServiceLifetime.Scoped)
        {
            ServiceLifetime = serviceLifetime;
            BaseType = baseType;
        }
    }

//C#11及以上的版本支持泛型Attribute
#if NET7\_0\_OR\_GREATER
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
    public class AutoInjectAttribute<T> : AutoInjectAttribute
    {
        public AutoInjectAttribute(ServiceLifetime serviceLifetime = ServiceLifetime.Scoped) : base(typeof(T), serviceLifetime)
        {
        }
    }
#endif
}

通过上面定义的Attribute我们就可以给我们的服务打上标记了

[AutoInject]
[AutoInject(ServiceLifetime.Singleton)]
[AutoInject(ServiceLifetime.Scoped)]
public class TestService : ITestService, ITest2Service
{
 public string Say(string message)
 {
 return $"hello {message}";
 }
 public string Say2(string message)
 {
 return message;
 }
}

[AutoInject]
[AutoInject(serviceLifetime: ServiceLifetime.Transient)]
[AutoInject(typeof(ITest2Service), ServiceLifetime.Scoped)]
public class TestService2 : ITest2Service
{
 public string Say2(string message)
 {
 return message;
 }
}

接下来就是Roslyn分析C#语法解析代码片段:
实现源生成器的唯一接口IIncrementalGenerator 实现Initialize方法:


private const string AttributeValueMetadataNameInject = "AutoInject";

/// 
/// 泛型AutoInjectAttribute
/// 
private const string GenericAutoInjectAttributeName = "Biwen.AutoClassGen.Attributes.AutoInjectAttribute`1";

/// 
/// 非泛型AutoInjectAttribute
/// 
private const string AutoInjectAttributeName = "Biwen.AutoClassGen.Attributes.AutoInjectAttribute";


#region 非泛型

//使用SyntaxProvider的ForAttributeWithMetadataName得到所有标注的服务集合
var nodesAutoInject = context.SyntaxProvider.ForAttributeWithMetadataName(
	AutoInjectAttributeName,
	(context, attributeSyntax) => true,
	(syntaxContext, _) => syntaxContext.TargetNode).Collect();

IncrementalValueProvider<(Compilation, ImmutableArray)> compilationAndTypesInject =
 context.CompilationProvider.Combine(nodesAutoInject);

#endregion

#region 泛型

var nodesAutoInjectG = context.SyntaxProvider.ForAttributeWithMetadataName(
GenericAutoInjectAttributeName,
(context, attributeSyntax) => true,
(syntaxContext, \_) => syntaxContext.TargetNode).Collect();

IncrementalValueProvider<(Compilation, ImmutableArray)> compilationAndTypesInjectG =
 context.CompilationProvider.Combine(nodesAutoInjectG);

#endregion

//合并所有的服务的编译类型
var join = compilationAndTypesInject.Combine(compilationAndTypesInjectG);


解下来我们定义一个Metadata类,该类主要定义Attribute的字段

private record AutoInjectDefine
{
	public string ImplType { get; set; } = null!;
	public string BaseType { get; set; } = null!;
	public string LifeTime { get; set; } = null!;
}

解析所有的标注泛型的Attribute metadata

private static List GetGenericAnnotatedNodesInject(Compilation compilation, ImmutableArray nodes)
{
 if (nodes.Length == 0) return [];
 // 注册的服务
 List autoInjects = [];
 List<string> namespaces = [];

 foreach (ClassDeclarationSyntax node in nodes.AsEnumerable().Cast())
 {
 AttributeSyntax? attributeSyntax = null;
 foreach (var attr in node.AttributeLists.AsEnumerable())
 {
 var attrName = attr.Attributes.FirstOrDefault()?.Name.ToString();
 attributeSyntax = attr.Attributes.First(x => x.Name.ToString().IndexOf(AttributeValueMetadataNameInject, System.StringComparison.Ordinal) == 0);

 if (attrName?.IndexOf(AttributeValueMetadataNameInject, System.StringComparison.Ordinal) == 0)
 {
 //转译的Entity类名
 var baseTypeName = string.Empty;

 string pattern = @"(?<=<)(?\w+)(?=>)";
 var match = Regex.Match(attributeSyntax.ToString(), pattern);
 if (match.Success)
 {
 baseTypeName = match.Groups["type"].Value.Split(['.']).Last();
 }
 else
 {
 continue;
 }

 var implTypeName = node.Identifier.ValueText;
 //var rootNamespace = node.AncestorsAndSelf().OfType().Single().Name.ToString();
 var symbols = compilation.GetSymbolsWithName(implTypeName);
 foreach (ITypeSymbol symbol in symbols.Cast())
 {
 implTypeName = symbol.ToDisplayString();
 break;
 }

 var baseSymbols = compilation.GetSymbolsWithName(baseTypeName);
 foreach (ITypeSymbol baseSymbol in baseSymbols.Cast())
 {
 baseTypeName = baseSymbol.ToDisplayString();
 break;
 }

 string lifeTime = "AddScoped"; //default
 {
 if (attributeSyntax.ArgumentList != null)
 {
 for (var i = 0; i < attributeSyntax.ArgumentList!.Arguments.Count; i++)
 {
 var expressionSyntax = attributeSyntax.ArgumentList.Arguments[i].Expression;
 if (expressionSyntax.IsKind(SyntaxKind.SimpleMemberAccessExpression))
 {
 var name = (expressionSyntax as MemberAccessExpressionSyntax)!.Name.Identifier.ValueText;
 lifeTime = name switch
 {
 "Singleton" => "AddSingleton",
 "Transient" => "AddTransient",
 "Scoped" => "AddScoped",
 \_ => "AddScoped",
 };
 break;
 }
 }
 }

 autoInjects.Add(new AutoInjectDefine
 {
 ImplType = implTypeName,
 BaseType = baseTypeName,
 LifeTime = lifeTime,
 });

 //命名空间
 symbols = compilation.GetSymbolsWithName(baseTypeName);
 foreach (ITypeSymbol symbol in symbols.Cast())
 {
 var fullNameSpace = symbol.ContainingNamespace.ToDisplayString();
 // 命名空间
 if (!namespaces.Contains(fullNameSpace))
 {
 namespaces.Add(fullNameSpace);
 }
 }
 }
 }
 }
 }

 return autoInjects;
}


解析所有标注非泛型Attribute的metadata集合


private static List GetAnnotatedNodesInject(Compilation compilation, ImmutableArray nodes)
{
 if (nodes.Length == 0) return [];
 // 注册的服务
 List autoInjects = [];
 List<string> namespaces = [];

 foreach (ClassDeclarationSyntax node in nodes.AsEnumerable().Cast())
 {
 AttributeSyntax? attributeSyntax = null;
 foreach (var attr in node.AttributeLists.AsEnumerable())
 {
 var attrName = attr.Attributes.FirstOrDefault()?.Name.ToString();
 attributeSyntax = attr.Attributes.FirstOrDefault(x => x.Name.ToString().IndexOf(AttributeValueMetadataNameInject, System.StringComparison.Ordinal) == 0);

 //其他的特性直接跳过
 if (attributeSyntax is null) continue;

 if (attrName?.IndexOf(AttributeValueMetadataNameInject, System.StringComparison.Ordinal) == 0)
 {
 var implTypeName = node.Identifier.ValueText;
 //var rootNamespace = node.AncestorsAndSelf().OfType().Single().Name.ToString();
 var symbols = compilation.GetSymbolsWithName(implTypeName);
 foreach (ITypeSymbol symbol in symbols.Cast())
 {
 implTypeName = symbol.ToDisplayString();
 break;
 }

 //转译的Entity类名
 var baseTypeName = string.Empty;

 if (attributeSyntax.ArgumentList == null || attributeSyntax.ArgumentList!.Arguments.Count == 0)
 {
 baseTypeName = implTypeName;
 }
 else
 {
 if (attributeSyntax.ArgumentList!.Arguments[0].Expression is TypeOfExpressionSyntax)
 {
 var eType = (attributeSyntax.ArgumentList!.Arguments[0].Expression as TypeOfExpressionSyntax)!.Type;
 if (eType.IsKind(SyntaxKind.IdentifierName))
 {
 baseTypeName = (eType as IdentifierNameSyntax)!.Identifier.ValueText;
 }
 else if (eType.IsKind(SyntaxKind.QualifiedName))
 {
 baseTypeName = (eType as QualifiedNameSyntax)!.ToString().Split(['.']).Last();
 }
 else if (eType.IsKind(SyntaxKind.AliasQualifiedName))
 {
 baseTypeName = (eType as AliasQualifiedNameSyntax)!.ToString().Split(['.']).Last();
 }
 if (string.IsNullOrEmpty(baseTypeName))
 {
 baseTypeName = implTypeName;
 }
 }
 else
 {
 baseTypeName = implTypeName;
 }
 }


 var baseSymbols = compilation.GetSymbolsWithName(baseTypeName);
 foreach (ITypeSymbol baseSymbol in baseSymbols.Cast())
 {
 baseTypeName = baseSymbol.ToDisplayString();
 break;
 }

 string lifeTime = "AddScoped"; //default
 {
 if (attributeSyntax.ArgumentList != null)
 {
 for (var i = 0; i < attributeSyntax.ArgumentList!.Arguments.Count; i++)
 {
 var expressionSyntax = attributeSyntax.ArgumentList.Arguments[i].Expression;
 if (expressionSyntax.IsKind(SyntaxKind.SimpleMemberAccessExpression))
 {
 var name = (expressionSyntax as MemberAccessExpressionSyntax)!.Name.Identifier.ValueText;
 lifeTime = name switch
 {
 "Singleton" => "AddSingleton",
 "Transient" => "AddTransient",
 "Scoped" => "AddScoped",
 \_ => "AddScoped",
 };
 break;
 }
 }
 }

 autoInjects.Add(new AutoInjectDefine
 {
 ImplType = implTypeName,
 BaseType = baseTypeName,
 LifeTime = lifeTime,
 });

 //命名空间
 symbols = compilation.GetSymbolsWithName(baseTypeName);
 foreach (ITypeSymbol symbol in symbols.Cast())
 {
 var fullNameSpace = symbol.ContainingNamespace.ToDisplayString();
 // 命名空间
 if (!namespaces.Contains(fullNameSpace))
 {
 namespaces.Add(fullNameSpace);
 }
 }
 }
 }
 }
 }
 return autoInjects;
}

通过上面的两个方法我们就取到了所有的Attribute的metadata,接下来的代码其实就比较简单了 原理就是将metadata转换为形如以下的代码:


#pragma warning disable
public static partial class AutoInjectExtension
{
    /// 
    /// 自动注册标注的服务
    /// 
    /// 
    /// 
    public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddAutoInject(this Microsoft.Extensions.DependencyInjection.IServiceCollection services)
    {
        services.AddScoped();
 services.AddTransient();
 // ...
 return services;
 }
}
#pragma warning restore


大致的代码如下:


context.RegisterSourceOutput(join, (ctx, nodes) =>
{
	var nodes1 = GetAnnotatedNodesInject(nodes.Left.Item1, nodes.Left.Item2);
	var nodes2 = GetGenericAnnotatedNodesInject(nodes.Right.Item1, nodes.Right.Item2);
	GenSource(ctx, [.. nodes1, .. nodes2]);
});

private static void GenSource(SourceProductionContext context, IEnumerable injectDefines)
{
	// 生成代码
	StringBuilder classes = new();
	injectDefines.Distinct().ToList().ForEach(define =>
	{
		if (define.ImplType != define.BaseType)
		{
			classes.AppendLine($@"services.{define.LifeTime}<{define.BaseType}, {define.ImplType}>();");
		}
		else
		{
			classes.AppendLine($@"services.{define.LifeTime}<{define.ImplType}>();");
		}
	});

	string rawNamespace = string.Empty;
	//\_namespaces.Distinct().ToList().ForEach(ns => rawNamespace += $"using {ns};\r\n");
	var envSource = Template.Replace("$services", classes.ToString());
	envSource = envSource.Replace("$namespaces", rawNamespace);
	// format:
	envSource = FormatContent(envSource);
	context.AddSource($"Biwen.AutoClassGenInject.g.cs", SourceText.From(envSource, Encoding.UTF8));
}
/// 
/// 格式化代码
/// 
/// 
/// 
private static string FormatContent(string csCode)
{
	var tree = CSharpSyntaxTree.ParseText(csCode);
	var root = tree.GetRoot().NormalizeWhitespace();
	var ret = root.ToFullString();
	return ret;
}

private const string Template = """
 // 
 // issue:https://github.com/vipwan/Biwen.AutoClassGen/issues
 // 如果你在使用中遇到问题,请第一时间issue,谢谢!
 // This file is generated by Biwen.AutoClassGen.AutoInjectSourceGenerator
 #pragma warning disable
 $namespaces
 public static partial class AutoInjectExtension
 {
 /// 
 /// 自动注册标注的服务
 /// 
 /// ">
 /// 
 public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddAutoInject(this Microsoft.Extensions.DependencyInjection.IServiceCollection services)
 {
 $services
 return services;
 }
 }
 
 #pragma warning restore
 """;


最终工具会自动为你生成以下代码:

// 
// issue:https://github.com/vipwan/Biwen.AutoClassGen/issues
// 如果你在使用中遇到问题,请第一时间issue,谢谢!
// This file is generated by Biwen.AutoClassGen.AutoInjectSourceGenerator
#pragma warning disable
public static partial class AutoInjectExtension
{
    /// 
    /// 自动注册标注的服务
    /// 
    /// 
    /// 
    public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddAutoInject(this Microsoft.Extensions.DependencyInjection.IServiceCollection services)
    {
        services.AddScoped();
 services.AddTransient();
 //...
 return services;
 }
}
#pragma warning restore


以上代码就完成了整个源生成步骤,最后你可以使用我发布的nuget包体验:

dotnet add package Biwen.AutoClassGen

源代码我发布到了GitHub,欢迎star! https://github.com/vipwan/Biwen.AutoClassGen

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

opencv图像处理详细讲(二)

联通组件分析 联通组件定义&#xff1a;像素值相同&#xff0c;通过四邻域或者八邻域相互连通的像素块。 换句话说&#xff0c;就是使用四邻域或八邻域的连通性&#xff0c;遍历图像的像素&#xff0c;并确定像素值相同并且连通的像素块&#xff0c;将它们标记为一个联通组件 两…

虚拟机VM VirtualBox安装openEuler+UKUI的安装和卸载_2024

虚拟机VM VirtualBox安装openEuler ps. 建议先看最后的其他 下载openEuler openEuler官网下载 一般来说标准版就够用了 使用虚拟机VM VirtualBox安装openEuler 新建虚拟机 修改用户名密码&#xff0c;建议修改&#xff0c;虽然之后还可以通过命令行修改&#xff08;注意密…

pyecharts绘制世界动态轨迹图(v0.5.X与v1.X版本对比)

一、问题引入 pyecharts官网&#xff1a;https://pyecharts.org/#/zh-cn/intro 在使用Geo或者GeoLines绘制动态轨迹图时&#xff0c;如果所选地区是中国的省份或者城市&#xff0c;是能够匹配到对应的经纬度并且正常绘制的&#xff1b;如果所选地区涉及到其他国家或者国外城市&…

Docker-harbor

一、搭建本地私有仓库 1.1 下载Registry镜像 1.2 添加本地私有仓库配置 1.3 重启服务并运行Registry容器 1.4.容器的操作 1.4.1 拉取Nginx镜像并为镜像打标签 1.4.2 上传到私有仓库 1.4.3 列出私有仓库所有镜像 1.4.4 列出私有仓库的镜像的所有标签 1.4.5 先删除原有…

Anaconda删除虚拟环境目录pkgs和envs|conda瘦身

这个文件夹里面是专门放不同环境中的包的&#xff0c;只是没有区分环境&#xff0c;都混在一起了&#xff0c; 一般在想要删除一个虚拟环境&#xff0c;除了在命令行中输入conda remove -n your_env_name(虚拟环境名称) --all 然后在envs中删除虚拟环境的文件夹&#xff0c; 还…

企业微信hook接口协议,ipad协议http,客户群发送任务,获取要发送的客户群列表

客户群发送任务&#xff0c;获取要发送的客户群列表 参数名必选类型说明uuid是String每个实例的唯一标识&#xff0c;根据uuid操作具体企业微信 请求示例 {"uuid": "1688853790533324","id":1101292747044333637, //群发任务id"keyword…

【Unity】如何获得TMP Button下的text内容

【背景】 unity项目中使用了TMP命名空间的Button UI组件。脚本中需要获得Button下Text的内容,但是发现用TextMeshPro仍然无法获得button下的text对象。 【分析】 Hierarchy结构上看明确Button下是有Text组件的: 括号里是TMP,所以理论上用TextMeshPro类型去FindComponent…

navicat 连接 阿里云 RDS mysql 数据库

首先上官方教程连接 下面是我的实操记录 1、先输入正确的账号、密码 2、再加上数据库名称

鸢尾花分类-pytorch实现

前言 本文用pytorch实现了鸢尾花分类&#xff0c;数据不多&#xff0c;只做代码展示用&#xff0c;后续有升级版本。 代码 -*- coding: utf-8 -*- File : main.py Author: Shanmh Time : 2024/05/06 上午9:37 Function&#xff1a;import torch from sklearn import datase…

算法(C++

题目&#xff1a;螺旋矩阵&#xff08;59. 螺旋矩阵 II - 力扣&#xff08;LeetCode&#xff09;&#xff09; 给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1&#xff1a; 输入&am…

Java常用5大集合类详解(实战)

文章目录 1、Collection1.1 iterator 迭代器 2、List 有序集合2.1 ArrayList ⭐2.2 LinkedList2.3 Queue 3、Set 无序集合3.1 HashSet ⭐3.2 TreeSet3.3 LinkedHashSet 4、Map 键值集合4.1 HashMap ⭐4.2 TreeMap / LinkedHashMap 5、工具类5.1 Collections5.2 Arrays 【拓展】…

Socket学习记录

本次学习Socket的编程开发&#xff0c;该技术在一些通讯软件&#xff0c;比如说微信&#xff0c;QQ等有广泛应用。 网络结构 这些都是计算机网络中的内容&#xff0c;我们在这里简单回顾一下&#xff1a; UDP(User Datagram Protocol):用户数据报协议;TCP(Transmission Contr…

厂家自定义 Android Ant编译流程源码分析

0、Ant安装 Windows下安装Ant&#xff1a; ant 官网可下载 http://ant.apache.org ant 环境配置&#xff1a; 解压ant的包到本地目录。 在环境变量中设置ANT_HOME&#xff0c;值为你的安装目录。 把ANT_HOME/bin加到你系统环境的path。 Ubuntu下安装Ant&#xff1a; sudo apt…

【数据结构】树和二叉树基本概念和性质

目录 前言1、树的概念1.1 树的基本概念1.2 树的主要概念1.3 树的表示1.4 树在实际中的运用&#xff08;表示文件系统的目录树结构&#xff09; 2. 二叉树概念及结构2.1 概念2.2 特殊的二叉树2.3 二叉树的性质 3. 二叉树性质相关选择题练习4. 答案和解析5. 总结 前言 本章带来数…

2024年03月 Scratch 图形化(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch图形化等级考试(1~4级)全部真题・点这里 一、单选题(共18题,共50分) 第1题 运行程序后,角色一定不会说出的数字是?( ) A:2 B:4 C:6 D:8 答案:A 程序中随机数的取值最小为 2,最大为 20 ,那么随机数加上 2 之后的结果的最小值为 4 ,最大值为 22 。所…

单目标问题的烟花优化算法求解matlab仿真,对比PSO和GA

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 单目标问题的FW烟花优化算法求解matlab仿真,对比PSO和GA。最后将FW&#xff0c;GA&#xff0c;PSO三种优化算法的优化收敛曲线进行对比。 2.测试软件版本以及运行…

【Android项目】“追茶到底”项目介绍

没有多的介绍&#xff0c;这里只是展示我的项目效果&#xff0c;后面会给出具体的代码实现。 一、用户模块 1、注册&#xff08;第一次登陆的话需要先注册账号&#xff09; 2、登陆&#xff08;具有记住最近登录用户功能&#xff09; 二、点单模块 1、展示饮品列表 2、双向联动…

T型槽地轨承载力是如何连接整个制造过程的强力桥梁(北重公司设计)

T型槽地轨承载力的定义和计算 T型槽地轨是一种用于工业设备运输和装配的关键组件。它由世界上各行各业的生产商广泛采用&#xff0c;其有效的承载力使其成为连接整个制造过程的强力桥梁。本文将介绍T型槽地轨的承载力以及相关的设计要点和应用。 承载力的定义和计算 承载力是…

【前沿模型解析】一致性模型CM(一)| 离散时间模型到连续时间模型数学推导

文章目录 1 离散时间模型2 连续时间模型 得到 SDE 随机微分方程2.1 从离散模型到SDE的推理步骤 3 补充&#xff1a;泰勒展开近似 1 − β i \sqrt{1-\beta_i} 1−βi​ ​ CM模型非常重要 引出了LCM等一系列重要工作 CM潜在性模型的数学公式推导并不好理解 一步一步&#xf…

微信个人号开发api接口-视频号矩阵接口-VIdeosApi

友情链接&#xff1a;VIdeosApi 获取用户主页 接口地址&#xff1a; http://api.videosapi.com/finder/v2/api/finder/userPage 入参 { "appId": "{{appid}}", "lastBuffer": "", "toUserName": "v2_060000231003b2…