首页
视频
资源
登录
原
.net core 3.1 制作一个简单的依赖注入容器
4101
人阅读
2021/2/18 21:39
总访问:
2607810
评论:
0
收藏:
0
手机
分类:
.net后台框架
![.netcore](https://img.tnblog.net/arcimg/hb/c857299a86d84ee7b26d181a31e58234.jpg ".netcore") >#.net core 3.1 制作一个简单的依赖注入容器 [TOC] 依赖注入简介 ------------ tn>IOC(控制反转)通过将一组通用的流程的控制权从应用转移到框架之中以实现对流程的复用,并按照好莱坞法则实现应用程序的代码与框架之间的交互。关于IOC模式有很多,其中比较有价值的是:依赖注入(DI) 大致结构图 ------------ ![](https://img.tnblog.net/arcimg/hb/7bfd3a1c234c463eaa32bd5c158f6f95.png) 项目架构如下 ------------ ![](https://img.tnblog.net/arcimg/hb/ab179c4a87c043a5907a949c6e18bde2.png) 生命周期 ------------ tn>首先我们需要了解在依赖注入中它一般有三种依赖注入的方式,我们这里分别定义为`Root`,`Self`与`Transient`方式,他们的生命周期如下图与列表所示。 ![](https://img.tnblog.net/arcimg/hb/6177645b4008433a86a8c1dd06bd2b26.png) | 生命周期 | 描述 | | ------------ | ------------ | | `Root` | 表示单例模式,唯一的一个 | | `Self` | 表示局部模式,在局部容器中创建 | | `Transient` | 表示瞬时模式,每次都重新创建一个新的实例 | >### 代码示例 ```csharp public enum Lifetime { Root, Self, Transient } ``` 实现服务注册类(ServiceRegistry) ------------ ![](https://img.tnblog.net/arcimg/hb/7a179cd204a842e9b340cbf5615a902e.png) tn>关于服务注册类,它具有3个核心属性(ServiceType,Lifetime与Factory),它们分别代表如下表所示。 | 属性 | 描述 | | ------------ | ------------ | | `ServiceType` | 服务类型。 | | `Lifetime` | 生命周期。 | | `Factory` | 用来创造的服务实例的工厂。这里使用的是`Func<AidasiDI,Type[],object>`类型,`AidasiDI`类表示容器注入主体,`Type[]`类型表示泛型参数 | | `Next` | 表示获取历史注册的服务,以单链表的形式连接起来。大家可以参考下图 | ![](https://img.tnblog.net/arcimg/hb/ba96a19c40f943878d8e79175601d6f9.png) ```csharp public class ServiceRegistry { public Type ServiceType { get; set; } public Lifetime Lifetime { get; set; } public Func<AidasiDI,Type[],object> Factory { get; set; } internal ServiceRegistry Next { get; set; } /// <summary> /// 获取该类型链表上所有的服务注册 /// </summary> /// <returns></returns> internal IEnumerable<ServiceRegistry> AsEnumerable() { var list = new List<ServiceRegistry>(); for (var OneServiceRegistry = this; OneServiceRegistry != null; OneServiceRegistry= OneServiceRegistry.Next) { list.Add(OneServiceRegistry); } return list; } } ``` 实现依赖注入容器类(AidasiDI) ------------ >### 编写属性与构造 tn>`AidasiDI`同时实现了`IServiceProvider`接口与`IDisposable`接口,通过实现`IServiceProvider`的`GetService`方法来进行提供服务实例。 ![](https://img.tnblog.net/arcimg/hb/5e027b2b6e5f458990656a1817d3614e.png) tn>在这里面也有四个比较重要的属性`_root`、`_ServiceRegisters`、`_InstancesServices`与`_disposables`。它们分别如下所示: | 属性 | 描述 | | ------------ | ------------ | | `_root` | 表示它的父节点,当它本身为根节点的时候该字段为`null` | | `_ServiceRegisters` | 注册的服务键值对集合,以类型`Type`为Key,以服务注册类`ServiceRegistry`为Value | | `_InstancesServices` | 注册后当前使用的服务实例的键值对集合。以类型`ServiceRegistryAndGenericity`为Key,它表示服务注册与泛型参数的组合;返回的`object`表示注册时需要返回的类型。 | | `_disposables` | 可以释放实例服务对象的集合 | | `_disposable` | 表示当前的实例是否正在释放,类型为`bool`类型。`volatile`关键字可以达到共享内存的操作节省资源 | ```csharp public class AidasiDI : IServiceProvider, IDisposable { public readonly AidasiDI _root; public readonly ConcurrentDictionary<Type,ServiceRegistry> _ServiceRegisters; public readonly ConcurrentDictionary<ServiceRegistryAndGenericity, object> _InstancesServices; public readonly ConcurrentBag<IDisposable> _disposables; public volatile bool _disposable; public AidasiDI() { _root = this; _ServiceRegisters = new ConcurrentDictionary<Type, ServiceRegistry>(); _InstancesServices = new ConcurrentDictionary<ServiceRegistryAndGenericity, object>(); _disposables = new ConcurrentBag<IDisposable>(); } public void Dispose() { throw new NotImplementedException(); } public object GetService(Type serviceType) { throw new NotImplementedException(); } } ``` >### 释放实例方法 ```csharp /// <summary> /// 释放实例 /// </summary> public void Dispose() { // 设置正在释放中 _disposable = true; // 释放相关服务实例 foreach (var item in _disposables) { item.Dispose(); } // 清除释放集合 _disposables.Clear(); // 清理释放服务的键值对 _InstancesServices.Clear(); } ``` tn>在这里当释放相关服务实例时,我们可以写一个`EnsureNotDisposed`方法,去判断当前实例是否是处于释放阶段,以防止有新的服务实例的获取。 ```csharp private void EnsureNotDisposed() { // 判断当前实例是否处于正在释放的阶段 if (_disposable) { // 如果是抛出异常 throw new ObjectDisposedException("AidasiDI _InstancesServices On Clear"); } } ``` >### 实现ServiceRegistryAndGenericity类 tn>这个类主要是服务注册与泛型参数的组合,所对应的字段分别是`Registry`与`GenericArguments`。并通过实现`IEquatable<ServiceRegistryAndGenericity>`接口实现`Equals`方法。 ```csharp public class ServiceRegistryAndGenericity:IEquatable<ServiceRegistryAndGenericity> { public ServiceRegistry Registry { get; } public Type[] GenericArguments { get; } public ServiceRegistryAndGenericity(ServiceRegistry _Registry,Type[] _GenericArguments) { Registry = _Registry; GenericArguments = _GenericArguments; } public bool Equals(ServiceRegistryAndGenericity other) { // 判断二者是否相等 if (Registry != other.Registry) { return false; } // 判断泛型参数长度是否相等 if (GenericArguments.Length != other.GenericArguments.Length) { return false; } // 判断泛型参数类型是否相等 for (int index = 0; index < GenericArguments.Length; index++) { if (GenericArguments[index] != other.GenericArguments[index]) { return false; } } return true; } /// <summary> /// 重新hash /// </summary> /// <returns></returns> public override int GetHashCode() { var hash = Registry.GetHashCode(); for (int i = 0; i < GenericArguments.Length; i++) { hash ^= GenericArguments[i].GetHashCode(); } return hash; } public override bool Equals(object obj) => obj is ServiceRegistryAndGenericity srag ? Equals(obj):false; } ``` >### 在AidasiDI中实现注册服务方法(Register) ![](https://img.tnblog.net/arcimg/hb/293b6ddeb6074420b9490880729a5569.png) tn>主要是将服务添加到`_ServiceRegisters`键值对中去,如下面的代码所示: ```csharp /// <summary> /// 实现注册方法 /// </summary> /// <returns></returns> public AidasiDI Register(ServiceRegistry serviceRegistry) { // 判断当前实例是否正在释放 EnsureNotDisposed(); // 在_ServiceRegisters中判断是否注册过,如果该服务有注册我们将修改为新的注册方式,并将历史注册放到新注册的next属性上 if (_ServiceRegisters.TryGetValue(serviceRegistry.ServiceType,out ServiceRegistry oldregistry)) { _ServiceRegisters[serviceRegistry.ServiceType] = serviceRegistry; serviceRegistry.Next = oldregistry; } else { _ServiceRegisters[serviceRegistry.ServiceType] = serviceRegistry; } return this; } ``` >### 实现获取服务核心方法(GetServiceCore) tn>大致逻辑是通过传入的`ServiceRegister`与泛型参数类型实例一个`ServiceRegistryAndGenericity`的类,然后通过判断生命周期的方式去获取实例。其中`Transient`会直接通过`_ServiceRegisters`的集合进行服务注册,不需要添加到已经实例的集合`_InstancesServices`中去,如果服务对象实现了`IDisposable`接口将会添加至释放`_disposables`集合中。 ```csharp private object GetServiceCore(ServiceRegistry serviceRegistry,Type[] GenericParam) { var ServiceKey = new ServiceRegistryAndGenericity(serviceRegistry, GenericParam); var ServiceLifetime = serviceRegistry.Lifetime; return ServiceLifetime switch { Lifetime.Root => GetOrCreate(_root._InstancesServices,_root._disposables), Lifetime.Self => GetOrCreate(_InstancesServices, _disposables), _ => LifetimeTransientGetOrCreate(_disposables), }; object LifetimeTransientGetOrCreate( ConcurrentBag<IDisposable> disposables) { // 获取新的实例 var InstanceService = serviceRegistry.Factory(this, GenericParam); // 如果可以释放,就添加到释放集合中去 if (InstanceService is IDisposable disposable) { disposables.Add(disposable); } return InstanceService; } object GetOrCreate(ConcurrentDictionary<ServiceRegistryAndGenericity, object> services, ConcurrentBag<IDisposable> disposables) { // 去实例集合中判断是否存在该类型的实例 if (services.TryGetValue(ServiceKey,out object InstanceService)) { // 如果有就直接返回 return InstanceService; } // 获取新的实例 InstanceService = serviceRegistry.Factory(this, GenericParam); // 添加到当前实例集合中去 services[ServiceKey] = InstanceService; // 如果可以释放,就添加到释放集合中去 if (InstanceService is IDisposable disposable) { disposables.Add(disposable); } return InstanceService; } } ``` >### 实现获取服务方法(GetService) tn>首先判断该实例是否已经释放了,如果没有释放我们需要判断它获取的是什么样的实例。最开始我们需要判断它是不是想获取当前的容器对象;接着就是它是不是想获取已经注册的服务历史集合;然后判断是泛型注入还是普通注入。 ```csharp public object GetService(Type serviceType) { EnsureNotDisposed(); // 判断是否获取的是当前容器对象 if (serviceType == typeof(AidasiDI) || serviceType == typeof(IServiceProvider)) { return this; } ServiceRegistry registry; // 判断是否是获取单个服务类型集合(IEnumerable) if (serviceType.IsGenericType && serviceType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) { // 获取集合的第一个类型,比如List<string> 那么就是 string 类型 var elementType = serviceType.GetGenericArguments()[0]; // 当它集合里面的类型,在我们服务注册集合(_ServiceRegisters)里面找不到的时候 if (!_ServiceRegisters.TryGetValue(elementType,out registry)) { // 我们将直接返回空的集合实例 return Array.CreateInstance(elementType, 0); } // 但如果找到有相关的服务注册,我们将获取该服务所有的历史注册对象 var registeries = registry.AsEnumerable(); var services = registeries.Select(x => GetServiceCore(x, Type.EmptyTypes)).ToArray(); Array array = Array.CreateInstance(elementType, services.Length); services.CopyTo(array, 0); return array; } // 泛型获取服务实例 if (serviceType.IsGenericType && !_ServiceRegisters.ContainsKey(serviceType)) { // 获取泛型参数类型 var definition = serviceType.GetGenericTypeDefinition(); return _ServiceRegisters.TryGetValue(serviceType, out registry) ? GetServiceCore(registry, definition.GetGenericArguments()) : null; } // 普通类型获取服务实例 return _ServiceRegisters.TryGetValue(serviceType, out registry) ? GetServiceCore(registry, new Type[0]) : null; } ``` 扩展方法 ------------ tn>这里我们需要运用一些外部扩展去调用服务的注册方法。在前面我们在获取服务具体实例时是通过`factory`的委托进行调用的,但这个委托我们并没有进行实现。我们这里就直接写道扩展方法`AidasiDIExtensions`类里面。 >### 实现实例委托方法Create tn>首先我们会去判断创建的实例是不是泛型,如果是泛型我们去获取泛型的具体类型;然后通过`constructors`获取公共构造方法的集合;紧接着我们这里我们获取有`InjectionAttribute`标签的公共构造优先(代码在该代码下面);如果没有找到该标签我们就获取第一个公共构造;然后获取构造里面需要实例的参数并用`GetService`进行创建。 ```csharp public static class AidasiDIExtensions { /// <summary> /// 最重要的创建实例方法 /// </summary> /// <param name="aidasiDI"></param> /// <param name="to"></param> /// <param name="arguments"></param> /// <returns></returns> private static object Create(AidasiDI aidasiDI, Type to, Type[] arguments) { if (arguments.Length > 0) { // 获取构造类型的类型对象 to = to.MakeGenericType(); } // 获取构造方法集合 var constructors = to.GetConstructors(); if (constructors.Length == 0) { throw new InvalidOperationException($"无法创建没有公共构造函数的{to}的实例。"); } // GetCustomAttributes(false) 不会获取自定义属性 // 有InjectionAttribute标记的公共构造优先 var constructor = constructors.FirstOrDefault(it => it.GetCustomAttributes(false).OfType<InjectionAttribute>().Any()); // 当没有该标记时,我们只需要选择第一个即可 constructor ??= constructors.First(); // 获取公共构造所需要的参数 var parameters = constructor.GetParameters(); // 当没有需要注入的构造参数,我们将直接实例 if (parameters.Length == 0) { return Activator.CreateInstance(to); } // 创建获取服务相关实例集合 var argumentinstances = new object[parameters.Length]; for (int index = 0; index < argumentinstances.Length; index++) { // [ParameterType]: 获取参数类型 argumentinstances[index] = aidasiDI.GetService(parameters[index].ParameterType); } return constructor.Invoke(argumentinstances); } } ``` ```csharp // AttributeUsage 声明一个Attribute的使用范围与使用原则。这里我们的范围指定为构造 // 更多请参考:https://www.runoob.com/csharp/csharp-attribute.html [AttributeUsage(AttributeTargets.Constructor)] public class InjectionAttribute:Attribute { } ``` >### 添加扩展方法 tn>添加注册范围的扩展与获取服务的扩展。 ```csharp public static class AidasiDIExtensions { public static AidasiDI Register<TTFrom, TTTo>(this AidasiDI aidasiDI, Lifetime lifetime) where TTTo : TTFrom => Register(aidasiDI,typeof(TTFrom),typeof(TTTo),lifetime); public static AidasiDI Register(this AidasiDI aidasiDI,Type from,Type to,Lifetime lifetime) { // 生成实例的工厂方法 Func<AidasiDI, Type[], object> func = (_, arguments) => Create(_, to, arguments); return aidasiDI.Register(new ServiceRegistry(from,lifetime,func)); } public static AidasiDI Register(this AidasiDI aidasiDI, Type ServiceType,object instance) { Func<AidasiDI, Type[], object> func = (_, arguments) => instance; return aidasiDI.Register(new ServiceRegistry(ServiceType, Lifetime.Root, func)); } public static AidasiDI Register<TSeervice>(this AidasiDI aidasiDI, TSeervice instance) { Func<AidasiDI, Type[], object> func = (_, arguments) => instance; return aidasiDI.Register(new ServiceRegistry(typeof(TSeervice), Lifetime.Root, func)); } public static AidasiDI Register(this AidasiDI aidasiDI,Type serviceType,Func<AidasiDI,object> factory, Lifetime lifetime) { Func<AidasiDI, Type[], object> func = (_, arguments) => factory(_); return aidasiDI.Register(new ServiceRegistry(serviceType, lifetime, func)); } public static AidasiDI Register<IService>(this AidasiDI aidasiDI, Func<AidasiDI, object> factory, Lifetime lifetime) { Func<AidasiDI, Type[], object> func = (_, arguments) => factory(_); return aidasiDI.Register(new ServiceRegistry(typeof(IService), lifetime, func)); } public static IService GetService<IService>(this AidasiDI aidasiDI) => (IService)aidasiDI.GetService(typeof(IService)); public static IEnumerable<IService> GetServices<IService>(this AidasiDI aidasiDI) => aidasiDI.GetService<IEnumerable<IService>>(); } ``` >### 运行测试 tn>首先我们定义一个`IDemo`接口,然后用`Demo`类实现`IDemo`与`IDisposable`。然后做一个小案例。 ```csharp static void Main(string[] args) { using (var service = new AidasiDI() .Register<IDemo,Demo>(Lifetime.Root) ) { service.GetService<IDemo>(); } Console.WriteLine("Exit"); Console.ReadKey(); } public interface IDemo{} public class Demo : IDemo, IDisposable { public void Dispose() { Console.WriteLine("Demo is Disposable"); } } ``` ![](https://img.tnblog.net/arcimg/hb/1fca7fd04899480eb7db0be97eb724ac.png) tn>我们发现还缺少局部子节点的区域运用。所以接下来我们需要添加子节点的代码。 实现子区域 ------------ tn>从下图中我们发现子区域就有一个`root`父亲节点的属性,并且会的服务注册也是从父亲节点那儿学的,只是其中的实例集合不一样。所以我们实现起来也非常之简单。 ![](https://img.tnblog.net/arcimg/hb/b126cbf4abf3462ca2cfa299a1edabab.png) >### 创建子区域构造方法 ```csharp public AidasiDI(AidasiDI aidasiDI) { _root = aidasiDI; _ServiceRegisters = aidasiDI._ServiceRegisters; _InstancesServices = new ConcurrentDictionary<ServiceRegistryAndGenericity, object>(); _disposables = new ConcurrentBag<IDisposable>(); } ``` >### 创建子区域的扩展方法 ```csharp public static AidasiDI CreateChild(this AidasiDI aidasiDI) => new AidasiDI(aidasiDI); ``` >### 修改代码测试一下 ```csharp static void Main(string[] args) { using (var service = new AidasiDI() .Register<IDemo,Demo>(Lifetime.Root) ) { using (var child = service.CreateChild()) { child.GetService<IDemo>(); } Console.WriteLine("End of subregion"); service.GetService<IDemo>(); } Console.WriteLine("Exit"); Console.ReadKey(); } ``` ![](https://img.tnblog.net/arcimg/hb/8030826ea8b6412cb60f422fd1481cad.png) 实现批量注册 ------------ tn>由于我们考虑到多个类需要注册,所以我们这里可以写一个特性类进行类的批量注册 ```csharp // AttributeUsage 声明一个Attribute的使用范围与使用原则。这里我们的范围指定为构造 [AttributeUsage(AttributeTargets.Class,AllowMultiple = true)] public sealed class MapToAttribute:Attribute { public Type ServiceType { get; } public Lifetime lifetime { get; } public MapToAttribute(Type ServiceType,Lifetime lifetime) { this.ServiceType = ServiceType; this.lifetime = lifetime; } } ``` tn>关于它应用的扩展方法 ```csharp public static AidasiDI Register(this AidasiDI aidasiDI,Assembly assembly) { // GetExportedTypes 构建程序集里public的类的实例时候,可以用这个函数得到这些类。 var typedAttributes = from type in assembly.GetExportedTypes() let attribute = type.GetCustomAttribute<MapToAttribute>() where attribute != null select new { ServiceType = type, Attribute = attribute }; foreach (var typedAttribute in typedAttributes) { aidasiDI.Register(typedAttribute.Attribute.ServiceType, typedAttribute.Attribute.lifetime); } return aidasiDI; } ``` tn>使用方法,在类的上面标注示例:`[MapTo(typeof(类型),Lifetime.Root)]`,在注册的时候我们将直接在扩展方法中使用`.Register(Assembly.GetEntryAssembly)`; 整合第三方依赖注入框架 ------------ >### 安装两个包 ```xml <ItemGroup> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.11" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.11" /> </ItemGroup> ``` >### 添加相关代码 tn>创建一个`AidasiBuilder`类。 ```csharp public class AidasiBuilder { private readonly AidasiDI _aidasiDI; public AidasiBuilder(AidasiDI aidasi) { _aidasiDI = aidasi; // 以瞬时的模式创建每一个范围服务(局部容器) _aidasiDI.Register<IServiceScopeFactory>(c => new ServiceScopeFactory(c.CreateChild()), Lifetime.Transient); } /// <summary> /// 实现服务范围(局部容器) /// </summary> private class ServiceScope : IServiceScope { public ServiceScope(IServiceProvider serviceProvider) { ServiceProvider = serviceProvider; } public IServiceProvider ServiceProvider { get; } public void Dispose() { (ServiceProvider as IDisposable)?.Dispose(); } } /// <summary> /// 实现创建范围服务的工厂 /// </summary> private class ServiceScopeFactory : IServiceScopeFactory { private readonly AidasiDI _aidasi; public ServiceScopeFactory(AidasiDI aidasi) => _aidasi = aidasi; public IServiceScope CreateScope() => new ServiceScope(_aidasi); } public IServiceProvider BuildServiceProvider() => _aidasiDI; /// <summary> /// 标签注册 /// </summary> /// <param name="assembly"></param> /// <returns></returns> public AidasiBuilder Register(Assembly assembly) { _aidasiDI.Register(assembly); return this; } } ``` tn>通过`AidasiServiceProviderFactory`实现`IServiceProviderFactory<AidasiBuilder>`类,在自定义的依赖注入框架中注册所需要的服务并提供具体的`IServiceProvider`相关方法。 ```csharp /// <summary> /// 实现 IServiceProviderFactory<AidasiBuilder> 方法 /// </summary> public class AidasiServiceProviderFactory : IServiceProviderFactory<AidasiBuilder> { public AidasiBuilder CreateBuilder(IServiceCollection services) { var aidasi = new AidasiDI(); foreach (var service in services) { // 通过工厂的方式进行注入 if (service.ImplementationFactory != null) { aidasi.Register(service.ServiceType, provider => service.ImplementationFactory(provider), service.Lifetime.AsAidasiLifetime()); } else if(service.ImplementationInstance != null) { // 通过不同的扩展注册服务 aidasi.Register(service.ServiceType, service.ImplementationInstance); } else { // 通过不同的扩展进行注册服务 aidasi.Register(service.ServiceType, service.ImplementationType, service.Lifetime.AsAidasiLifetime()); } } // 返回对应的ContainerBuilder return new AidasiBuilder(aidasi); } /// <summary> /// 获取IServiceProvider /// </summary> /// <param name="containerBuilder"></param> /// <returns></returns> public IServiceProvider CreateServiceProvider(AidasiBuilder containerBuilder) { return containerBuilder.BuildServiceProvider(); } } ``` tn>添加生命周期转换的扩展方法与添加该依赖注入的方法。 ```csharp internal static class Extensions { public static Lifetime AsAidasiLifetime(this ServiceLifetime service) => service switch { ServiceLifetime.Scoped => Lifetime.Self, ServiceLifetime.Singleton => Lifetime.Root, ServiceLifetime.Transient => Lifetime.Transient, _ => throw new Exception($"{typeof(ServiceLifetime)} Can't Convert") }; public static IServiceProvider AddAiDasiService(this IServiceCollection services) { // 实例创建范围服务的工厂 var factory = new AidasiServiceProviderFactory(); // 创建Builder获取IServiceProvider return factory.CreateBuilder(services) .Register(Assembly.GetEntryAssembly()) .BuildServiceProvider(); } } ``` tn>修改Program类 ```csharp static void Main(string[] args) { // 这里不用root的原因是它本身就是根节点 var services = new ServiceCollection() .AddSingleton<IDemo, Demo>(); // 使用Aidasi依赖注入框架 var _ServiceProvider = services.AddAiDasiService(); // 创建区域范围 using (var child = _ServiceProvider.CreateScope()) { child.ServiceProvider.GetService<IDemo>(); } Console.WriteLine("End of subregion"); _ServiceProvider.GetService<IDemo>(); Console.WriteLine("Exit"); Console.ReadKey(); } public interface IDemo { } public class Demo : IDemo, IDisposable { public void Dispose() { Console.WriteLine("Demo is Disposable"); } } ``` tn>运行测试 ![](https://img.tnblog.net/arcimg/hb/e31d1c93709e44cc8366941c28345ea5.png)
欢迎加群讨论技术,1群:677373950(满了,可以加,但通过不了),2群:656732739
👈{{preArticle.title}}
👉{{nextArticle.title}}
评价
{{titleitem}}
{{titleitem}}
{{item.content}}
{{titleitem}}
{{titleitem}}
{{item.content}}
尘叶心繁
这一世以无限游戏为使命!
博主信息
排名
6
文章
6
粉丝
16
评论
8
文章类别
.net后台框架
168篇
linux
17篇
linux中cve
1篇
windows中cve
0篇
资源分享
10篇
Win32
3篇
前端
28篇
传说中的c
4篇
Xamarin
9篇
docker
15篇
容器编排
101篇
grpc
4篇
Go
15篇
yaml模板
1篇
理论
2篇
更多
Sqlserver
4篇
云产品
39篇
git
3篇
Unity
1篇
考证
2篇
RabbitMq
23篇
Harbor
1篇
Ansible
8篇
Jenkins
17篇
Vue
1篇
Ids4
18篇
istio
1篇
架构
2篇
网络
7篇
windbg
4篇
AI
18篇
threejs
2篇
人物
1篇
嵌入式
2篇
python
13篇
HuggingFace
8篇
pytorch
9篇
opencv
6篇
最新文章
最新评价
{{item.articleTitle}}
{{item.blogName}}
:
{{item.content}}
关于我们
ICP备案 :
渝ICP备18016597号-1
网站信息:
2018-2024
TNBLOG.NET
技术交流:
群号656732739
联系我们:
contact@tnblog.net
欢迎加群
欢迎加群交流技术