C# “依赖注入” 中的 “三种生命周期”

🚀简介

依赖注入(Dependency Injection,简称DI)是一种实现控制反转(IoC)的技术,用于减少代码之间的耦合度。通过依赖注入,一个类可以从外部获取其依赖的对象,而不是自己创建。这样可以使得代码更加模块化,更易于测试和维护。

🐳依赖注入的生命周期主要有以下三种:

  1. Transient:每次请求都会创建一个新的实例。这是最常见的生命周期选项。
  2. Scoped:在同一次请求中始终返回同一实例。如果在不同的请求中,将会创建一个新的实例。
  3. Singleton:每次请求都返回同一个实例,即在首次请求时创建的实例。

🚀安装NuGet包

首先,我们先安装 Microsoft.Extensions.DependencyInjection:这是Microsoft提供的依赖注入框架。

🚀三种生命周期演示

Transient

创建一个类,包含一个属性(Name)和一个方法(SayHi)

public class TestServicesImp
{
    public string Name { get; set; }
    
    public void SayHi()
    {
        Console.WriteLine("Hello:"+Name);
    }
}

👻通过ServiceCollection创建了一个服务容器,然后通过AddTransient方法将TestServicesImp类型注册到这个服务容器中。这里使用的是瞬时生命周期,也就是每次从容器中获取TestServicesImp类型时,都会创建一个新的实例。

public class Program
{
    public static void Main(string[] args)
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddTransient<TestServicesImp>();
        using (ServiceProvider sp = serviceCollection.BuildServiceProvider())
        {
            var t1 = sp.GetService<TestServicesImp>();
            t1.Name = "张三";
            t1.SayHi();

            var t2 = sp.GetService<TestServicesImp>();
            t2.Name = "李四";
            t2.SayHi();

            var referenceEquals = object.ReferenceEquals(t1, t2);
            Console.WriteLine(referenceEquals);
        }
    }
}

运行结果!因为在瞬时生命周期中,每次获取服务都会创建新的实例。 因此通过object.ReferenceEquals方法比较了t1和t2是否是同一个实例,结果会输出false

 Scoped

我们将上面代码中的服务注册AddTransient方法换成AddScoped,然后t2.SayHi();换成t1.SayHi();,再试一次。

    public static void Main(string[] args)
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddScoped<TestServicesImp>();
        using (ServiceProvider sp = serviceCollection.BuildServiceProvider())
        {
            var t1 = sp.GetService<TestServicesImp>();
            t1.Name = "张三";
            t1.SayHi();

            var t2 = sp.GetService<TestServicesImp>();
            t2.Name = "李四";
            t1.SayHi();

            var referenceEquals = object.ReferenceEquals(t1, t2);
            Console.WriteLine(referenceEquals);
        }
    }

发现这次的比较结果为true,并且第二次调用SayHi也是使用t1,但依然输出了李四,因此可以得出,两次获取服务得到的是同一个实例。

 👻我们对代码再进行改造一下,创建一个新的服务作用域,通过这个作用域的ServiceProvider对象获取了另一个TestServicesImp的实例t2。

    public static void Main(string[] args)
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddScoped<TestServicesImp>();
        using (ServiceProvider sp = serviceCollection.BuildServiceProvider())
        {
            var t1 = sp.GetService<TestServicesImp>();
            t1.Name = "张三";


            using (IServiceScope scope = sp.CreateScope())
            {
                var t2 = scope.ServiceProvider.GetService<TestServicesImp>();
                t2.Name = "李四";

                t1.SayHi();
                t2.SayHi();
                
                var referenceEquals = object.ReferenceEquals(t1, t2);
                Console.WriteLine(referenceEquals);
            }
        }
    }

因为它们是在不同的Scope中获取的,所以这次的结果是false

Singleton 

我们将服务注册AddScoped方法,换成AddSingleton,然后再执行一次

    public static void Main(string[] args)
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddSingleton<TestServicesImp>();
        using (ServiceProvider sp = serviceCollection.BuildServiceProvider())
        {
            var t1 = sp.GetService<TestServicesImp>();
            t1.Name = "张三";


            using (IServiceScope scope = sp.CreateScope())
            {
                var t2 = scope.ServiceProvider.GetService<TestServicesImp>();
                t2.Name = "李四";

                t1.SayHi();
                t2.SayHi();
                
                var referenceEquals = object.ReferenceEquals(t1, t2);
                Console.WriteLine(referenceEquals);
            }
        }
    }

在输出结果中很显然可以看出使用AddSingleton注册服务,每次请求都返回同一个实例

 🚀总结

使用场景:

  • 减少模块间的耦合:通过依赖注入,组件之间的直接依赖被移除,取而代之的是接口的依赖。这有助于降低系统各部分间的耦合度,使得每个组件更加独立,便于单独开发和测试。

  • 易于测试:依赖注入使得单元测试变得更加简单。可以注入伪对象(mock objects),来模拟复杂的依赖关系,从而专注于当前组件的行为测试。

优点:

  • 提高代码的可维护性:依赖注入使代码结构清晰,每个组件的职责单一,易于理解和维护。

  • 提高系统的灵活性和扩展性:由于组件间依赖的是抽象而不是具体实现,更换或升级系统的某个部分变得更加容易,支持系统的快速迭代和扩展。

缺点:

  • 可能导致性能开销:在某些情况下,依赖注入框架在解析依赖时可能会引入额外的性能开销,特别是在启动时间和内存消耗上。

  • 过度依赖容器:过度使用依赖注入有可能导致开发者过度依赖IoC容器来管理对象,从而忽视了对象生命周期和资源管理的重要性。

在实际开发中,应结合项目具体情况,权衡利弊,做出恰当的设计选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

爱吃香蕉的阿豪

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

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

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

打赏作者

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

抵扣说明:

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

余额充值