.Net 中使用依赖注入(DI)(一)

本文介绍.NET框架中依赖注入(DI)的基本概念及其应用场景,通过对比传统接口调用方式,展示了DI如何减少模块间的耦合度,并详细解释了DI的三种生命周期:瞬态(Transient)、单例(Singleton)及范围(Scoped),帮助读者更好地掌握DI的使用。

.Net 中使用 DI

依赖注入(Dependency Injection,DI)是控制反转(Inversion of Control,IOC)思想的实现方式。

依赖注入简化模块的组装过程,降低模块之间的耦合度

为什么使用DI?

假设有这样的接口和实现类:

public interface ISayHello
{
    public void SayHello();
}

public class Chinese : ISayHello, IDisposable
{
    public void SayHello()
    {
        Console.WriteLine("你好!");
    }

    public void Dispose()
    {
        Console.WriteLine("资源释放了");
    }
}

调用方式:

ISayHello sayHello = new Chinese();
sayHello.SayHello();

基于接口编程自有其好处,但是如上面的方式还是有缺陷的。调用者必须知道是谁实现了ISayHello接口的SayHello方法。这里就需要知道一个接口,一个实现类,加上本身调用的地方,一个简单的调用就有三者耦合。

DI 原理

下面让我们通过 DI 的方式实现,首先是 nuget 安装:

Install-Package Microsoft.Extensions.DependencyInjection -Version 6.0.0

上面包是微软实现的实现了依赖注入的包,看看怎么使用(最麻烦的使用方式):

ServiceCollection serviceContainer = new ServiceCollection();  // 构建一个服务容器
serviceContainer.AddTransient<Chinese>();  // 把 Chinese 作为瞬态服务(对象),添加到服务器容器

// 通过容器构建一个 ServiceProvider(服务提供器),ServiceProvider 可以提供容器中管理的服务(已添加到容器的服务)
using (ServiceProvider serviceProvider = serviceContainer.BuildServiceProvider())  
{
    ISayHello sayHello = serviceProvider.GetService<Chinese>();  // 从 ServiceProvider 中获取需要的服务(对象)
    sayHello.SayHello();
}
  1. 首先创建一个服务(对象)容器
  2. 把需要服务容器管理的服务(对象)添加到服务容器
  3. 通过服务容器构建一个 ServiceProvider(服务提供器),ServiceProvider 可以提供容器管理的服务(已添加到容器的服务)
  4. 通过 ServiceProvider(服务提供器)获取需要的服务(对象)

DI 的生命周期

Transient(瞬态)

使用一次即销毁,下次使用将是一个新的对象

Singleton(单例)

顾名思义,参考单例模式

Scoped(范围)

在一个范围内使用同一个对象

可以通过ServiceProviderCreateScope方法创建新的范围:

ServiceCollection serviceContainer = new ServiceCollection();
serviceContainer.AddScoped<Chinese>();

using (ServiceProvider serviceProvider = serviceContainer.BuildServiceProvider())
{
    ISayHello sayHello1 = serviceProvider.GetService<Chinese>();
    ISayHello sayHello2 = serviceProvider.GetService<Chinese>();
    ISayHello sayHello3 = null;  // 应该在 Scope 范围内声明变量
    ISayHello sayHello4 = null;  // 应该在 Scope 范围内声明变量
    ISayHello sayHello5 = null;  // 应该在 Scope 范围内声明变量
    ISayHello sayHello6 = null;  // 应该在 Scope 范围内声明变量

    using (IServiceScope serviceScope = serviceProvider.CreateScope())
    {
        sayHello3 = serviceScope.ServiceProvider.GetService<Chinese>();
        sayHello4 = serviceScope.ServiceProvider.GetService<Chinese>();
    }

    using (IServiceScope serviceScope = serviceProvider.CreateScope())
    {
        sayHello5 = serviceScope.ServiceProvider.GetService<Chinese>();
        sayHello6 = serviceScope.ServiceProvider.GetService<Chinese>();
    }

    Console.WriteLine(Object.ReferenceEquals(sayHello1, sayHello2));  // True,  都是通过 serviceProvider 创建
    Console.WriteLine(Object.ReferenceEquals(sayHello3, sayHello4));  // True, 都是通过同一个 serviceScope 创建,在单独的一个范围中
    Console.WriteLine(Object.ReferenceEquals(sayHello1, sayHello4));  // False,在单独的一个范围中,所以和 serviceProvider 创建的不一致
    Console.WriteLine(Object.ReferenceEquals(sayHello5, sayHello6));  // True, 同 sayHello3, sayHello4
    Console.WriteLine(Object.ReferenceEquals(sayHello1, sayHello6));  // False,同 sayHello1, sayHello4
    Console.WriteLine(Object.ReferenceEquals(sayHello4, sayHello6));  // False,在两个 serviceScope 中,所以 False
}

在这里插入图片描述

为了确保对象正确的生命周期,应该在 Scope 范围内声明变量

由上图可以知道,如果实现类实现了 IDisposable接口,则在离开作用域之后,容器会自动调用对象的Dispose方法

也印证了需要在范围内声明并使用对象,在外面可能资源被释放了

如何选择生命周期?

如果一个类是无状态的,这时可以选择 Singleton。因为无状态的类无所谓并发问题(不会修改状态)
如果一个类有状态,并且有 Scope 控制,可以注册为 Scoped。因为 Scope 控制的代码都是运行在同一个线程中的,没有并发修改的问题
最后 Transient 在使用时,需要谨慎,因为每次都会创建一个新的对象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Qanx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值