C#依赖注入和仿写Prism注入
摘要:本文介绍了.NET原生依赖注入和Prism框架的依赖注入实现方式。.NET原生DI通过IServiceCollection注册服务、IServiceProvider解析服务,支持Singleton、Scoped和Transient三种生命周期。重点分析了构造函数注入机制,通过容器获取实例时自动完成依赖注入。对比了单例注入与单例设计模式的差异,指出依赖注入更便于测试和项目管理。Prism框架在
我们日常使用中会经常使用到依赖注入的情况。将服务类或者接口注入到DI容器中。并且在WPF Prism框架中会经常使用到Prism的依赖注入。Prism的依赖注入与.Net的原生注入是一致的,Prism的依赖注入相当于原生的超集。Prism的依赖注入在原生注入的情况下还增加了ViewModel的依赖注入。
除了普通的依赖注入外,我们比较多常见的差异在于单例注入和单例设计模式的差异。
首先先介绍一下原生.Net的依赖注入的作用和用法。
一、.NET 原生依赖注入
1. 原生 DI 的核心组件
.NET 原生 DI 体系封装在Microsoft.Extensions.DependencyInjection命名空间下,主要组件包括:
IServiceCollection:服务注册容器,声明 “接口 - 实现类” 映射及生命周期
IServiceProvider:服务解析容器,创建 / 获取服务实例(核心:容器接管实例生命周期)
ServiceLifetime:生命周期枚举:Singleton(单例)、Scoped(作用域)、Transient(瞬时)
2. 构造函数注入
在构造函数注入中,构造函数注入的传参从哪里来的问题,这个很多人都不了解前后因果关系。我们都知道依赖注入的参数一定是来自与DI容器的。比如我们有一个单例注入的对象,将对象注册到DI容器中,然后在其他地方进行访问。那么我现在的传参类对象到底是不是从DI容器来的,构造函数是怎么从DI容器中拿到单例类对象的。
这里就涉及到,我们怎么从DI容器获取类对象的事情。首先我们先将单例注册到容器中,然后将使用类也注册到容器中,在我们注册的操作中,就相当于容器接管了我们的类对象。下面做一个代码参考
参考Github地址:https://github.com/2825077535/DIABInstance
namespace InstanceTest
{
public interface ILogService
{
// 标记单例实例的唯一ID,验证全局唯一性
Guid InstanceId { get; }
void Log(string message);
}
}
namespace InstanceTest
{
public class LogService : ILogService
{
// 单例实例唯一标识
public Guid InstanceId { get; } = Guid.NewGuid();
public void Log(string message)
{
Console.WriteLine($"[B项目单例 {InstanceId}] {message}");
}
}
}
namespace InstanceTest
{
public class OrderService
{
private readonly ILogService _logService;
// 核心:构造函数依赖注入B项目的ILogService单例
public OrderService(ILogService logService=null)
{
// 若注入失效,此处会抛空引用异常
_logService = logService ?? throw new ArgumentNullException(
nameof(logService), "ILogService注入失败!容器未接管实例");
}
// 业务方法:使用注入的单例服务
public void ProcessOrder(int orderId)
{
_logService.Log($"处理订单 {orderId}");
}
}
}
using InstanceTest;
using Microsoft.Extensions.DependencyInjection;
using System.ComponentModel;
using System.Xml.Linq;
var services = new ServiceCollection();
services.AddSingleton<ILogService, LogService>(); // 注册单例
services.AddScoped<OrderService>();
var provider = services.BuildServiceProvider();
try
{
var order=new OrderService();
var orderService1 = provider.GetRequiredService<OrderService>();
orderService1.ProcessOrder(1001);
}
catch (Exception ex)
{
Console.WriteLine($"注入失效原因:{ex.Message}");
}
if (true)
{
var orderService1 = provider.GetRequiredService<OrderService>();
orderService1.ProcessOrder(1002);
}
上述代码中,如果我们直接实例new一个OrderService对象,那么我们构造函数的传参就为空值。如果我们通过容器来创建OrderService的对象时,就自动将容器对应的单例类对象注入其中了。
3.单例注入和单例设计模式
单例设计模式与单例注入,主要差异来自于项目管理上的问题。
首先单例设计模式在项目上不方便进行黑盒测试。黑盒测试的2大前提,无状态依赖和可独立执行。在单例设计模式中,每一次的执行都会将当前执行的数据保存到下一次执行中。而依赖注入的单例模式是依靠于容器实现的。那么不同的容器可以关联不同的单例类。我们可以在每个黑盒测试中都创建新的容器用于管理单例类。
其次,在相当多的C#项目情况下都会用到依赖注入的形式,比如WPF的Prism框架,可以运行在ViewModel中进行单例传参,便于项目的管理。
二、Prism框架的依赖注入
我们可以通过放些Prism框架的容器来了解Prism框架的依赖注入。Prism框架的依赖注入是在原生的依赖注入上实现的。Prism框架的容器与我们平时容器不一样就是,Prism使用全局容器进行管理,在整个框架的使用上会更加友好,当然逻辑并不复杂。我们可以在自己的项目上定义全局容器来管理我们的类对象即可。
github地址:https://github.com/2825077535/PrismTest.git
namespace DI.Core.Interfaces
{
// 模拟 Prism 的 IContainerProvider(解析服务)
public interface IContainerProvider
{
// 解析服务(对应 Prism 的 Resolve)
T Resolve<T>() where T : class;
}
}
namespace DI.Core.Interfaces
{
// 模拟 Prism 的 IContainerRegistry(注册服务)
public interface IContainerRegistry
{
// 注册单例(对应 Prism 的 RegisterSingleton)
void RegisterSingleton<T>() where T : class;
// 注册瞬时
void Register<T>() where T : class;
}
}
namespace DI.Core
{
// 全局容器管理类(单例,模拟 PrismApplication 的 Container)
public static class ContainerLocator
{
private static IServiceProvider _serviceProvider;
private static IServiceCollection _services;
// 初始化容器
public static IContainerRegistry CreateContainer()
{
_services = new ServiceCollection();
return new ContainerRegistry(_services);
}
// 构建容器(注册完成后调用)
public static IContainerProvider Build()
{
_serviceProvider = _services.BuildServiceProvider();
return new ContainerProvider(_serviceProvider);
}
// 全局访问容器(对应 Prism 的 Container 属性)
public static IContainerProvider Current => new ContainerProvider(_serviceProvider);
// 内部实现:注册逻辑
private class ContainerRegistry : IContainerRegistry
{
private readonly IServiceCollection _services;
public ContainerRegistry(IServiceCollection services) => _services = services;
// DI 注册单例
public void RegisterSingleton<T>() where T : class
{
_services.AddSingleton<T>();
}
public void Register<T>() where T : class
{
_services.AddTransient<T>();
}
}
// 内部实现:解析逻辑
private class ContainerProvider : IContainerProvider
{
private readonly IServiceProvider _provider;
public ContainerProvider(IServiceProvider provider) => _provider = provider;
//DI 解析服务
public T Resolve<T>() where T : class
{
return _provider.GetRequiredService<T>();
}
}
}
}
namespace PrismUse
{
public class MySingletonService
{
private readonly string _instanceId;
//自动调用构造函数
public MySingletonService()
{
_instanceId = Guid.NewGuid().ToString("N").Substring(0, 8);
Console.WriteLine($"[PrismUse 项目] MySingletonService 单例创建,ID:{_instanceId}");
}
// 业务方法
public void Execute()
{
Console.WriteLine($"[PrismUse 项目] 执行单例逻辑,ID:{_instanceId}");
}
// 验证单例唯一性
public string GetInstanceId() => _instanceId;
}
}
namespace PrismUse
{
public class SingletonUser
{
private readonly MySingletonService _singletonService;
// 方式 1:构造函数注入
public SingletonUser(MySingletonService singletonService)
{
_singletonService = singletonService;
Console.WriteLine("[PrismUse 项目] SingletonUser 构造注入单例成功");
}
// 调用单例方法
public void UseSingleton()
{
_singletonService.Execute();
}
// 方式 2:全局容器解析
public static void UseSingletonManually()
{
// 模拟 Prism 全局容器访问
var singleton = ContainerLocator.Current.Resolve<MySingletonService>();
Console.WriteLine($"[PrismUse 项目] 手动解析单例,ID:{singleton.GetInstanceId()}");
}
}
}
using DI.Core;
using DI.Core.Interfaces;
using PrismUse;
IContainerRegistry registry = ContainerLocator.CreateContainer();
// ===== 步骤 2:注册 PrismUse 项目的单例=====
// 对应 Prism 的 RegisterSingleton
registry.RegisterSingleton<MySingletonService>();
// 可选:注册 PrismUse 项目的使用类
registry.Register<SingletonUser>();
// ===== 步骤 3:构建容器=====
IContainerProvider provider = ContainerLocator.Build();
// ===== 验证 1:PrismTest 项目直接解析 PrismUse 项目单例 =====
var singleton1 = provider.Resolve<MySingletonService>();
var singleton2 = provider.Resolve<MySingletonService>();
singleton1.Execute();
// 验证单例唯一性
Console.WriteLine($"[PrismTest 项目] 单例是否唯一:{ReferenceEquals(singleton1, singleton2)}"); // True
// ===== 验证 2:PrismTest 项目解析 PrismUse 项目的使用类(自动注入)=====
var user = provider.Resolve<SingletonUser>();
user.UseSingleton();
// ===== 验证 3:PrismUse 项目内部手动解析全局容器 =====
SingletonUser.UseSingletonManually();
Console.ReadKey();
三、常见错误
1.没有注册类
我们在使用类的构造函数注入时,需要将类在容器内标记为接管模式。
//例如
services.AddScoped<OrderService>();
//将OrderService注册到services的容器中,将OrderService在容器中标记为接管模式
如果没有标记为接管模式时,容器将不能创建这个对象会直接报错。
2.直接new一个对象而不是用容器来创建
//直接new一个新的对象,容器不能正常注入
var order=new OrderService();
//使用容器来创建类对象,容器可以正常进行构造函数注入
var orderService1 = provider.GetRequiredService<OrderService>();
3.对单例类进行依赖注入导致类对象生命周期不终止
将IServiceScopeFactory 注入到单例类容器中,在使用scope.ServiceProvider.GetRequiredService();创建一个只有在作用域内的新的注入类对象
//假设OrderService为容器的单例类
public class OrderService
{
private readonly IServiceScopeFactory _scopeFactory;
public OrderService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public void ProcessOrder(int orderId)
{
using var scope = _scopeFactory.CreateScope();
var logService = scope.ServiceProvider.GetRequiredService<ILogService>();
logService.Log($"处理订单 {orderId}");
}
}
更多推荐


所有评论(0)