依赖注入学习笔记

概念

依赖注入(dependency injection DI)是控制反转(inversion of control, IOC)思想的实现方式。
依赖注入简化模块的组装过程,降低模块之间的耦合度

IOC目的

IOC的目的:“怎样创建xxx对象”------>“我要xxx对象”
不关注细节,只关注业务

IOC实现方式

  1. 服务定位器 serviceLocator
  2. 依赖注入 DI

DI几个概念

  1. 服务(service): 你向框架要的对象
  2. 注册服务
  3. 服务容器:负责管理注册的服务
  4. 查询服务:创建对象及关联对象
  5. 对象生命周期:Transient(瞬态)、scoped(范围)、singleton(单例)

.net中使用DI

1 服务定位器

来看个例子

    private static void Main(string[] args)
    {
        // 之前的调用方法
        ITestService test = new TestService1();
        test.Name = "hahha";
        test.SayHi();

        ITestService test2 = new TestService2();
        test2.Name = "ttt";
        test2.SayHi();

    }

    public interface ITestService
    {
        public string Name { get; set; }

        public void SayHi();
    }

    public class TestService1 : ITestService
    {
        public string Name { get; set; }

        public void SayHi()
        {
            Console.WriteLine($"hello {Name}");
        }
    }

    public class TestService2 : ITestService
    {
        public string Name { get; set; }

        public void SayHi()
        {
            Console.WriteLine($"你好 {Name}");
        }
    }
  1. 在.net中,是根据类型来获取和注册服务的。类型可以分别值服务类型(service type)和实现类型(implementation type).这两者可能相同,也可能不同。服务类型可以是接口,也可以是类,建议面向接口编程,更灵活。
  2. .net IOC组件取名为DependencyInjection,但它包含serviceLocator的功能。
  3. 通过nuget添加DependencyInjection包
    private static void Main(string[] args)
    {
        // ioc写法
        ServiceCollection serviceollection = new ServiceCollection();
        serviceollection.AddTransient<TestService1>();
        using (ServiceProvider sp = serviceollection.BuildServiceProvider())
        {
            TestService1 t1 = sp.GetService<TestService1>();
            t1.Name = "ioc1";
            t1.SayHi();
        }
    }

生命周期

  1. 给类构造函数中打印,看看不同生命周期的对象创建,使用serviceProvider.CreateScope()创建Scope。

Transient 每次都创建一个新的对象

    private static void Main(string[] args)
    {
        // ioc写法
        ServiceCollection serviceollection = new ServiceCollection();
        serviceollection.AddTransient<TestService1>(); // 瞬时生命周期 每次都创建一个新的对象
        using (ServiceProvider sp = serviceollection.BuildServiceProvider())
        {
            TestService1 t1 = sp.GetService<TestService1>();
            t1.Name = "ioc1";
            t1.SayHi();

            TestService1 t2 = sp.GetService<TestService1>(); // 再次创建对象
            Console.WriteLine(object.ReferenceEquals(t1, t2));// 看看是否是同一个对象
        }
    }

在这里插入图片描述
Singleton 每次都创建同一个对象

private static void Main(string[] args)
    {
        // ioc写法
        ServiceCollection serviceollection = new ServiceCollection();
        serviceollection.AddSingleton<TestService1>(); // 单例生命周期 每次都创建同一个对象
        using (ServiceProvider sp = serviceollection.BuildServiceProvider())
        {
            TestService1 t1 = sp.GetService<TestService1>();
            t1.Name = "ioc1";
            t1.SayHi();

            TestService1 t2 = sp.GetService<TestService1>(); // 再次创建对象
            Console.WriteLine(object.ReferenceEquals(t1, t2));// 看看是否是同一个对象
        }
    }

在这里插入图片描述

    private static void Main(string[] args)
    {
        // ioc写法
        ServiceCollection serviceollection = new ServiceCollection();
        serviceollection.AddScoped<TestService1>(); // 范围生命周期 同一个范围内创建的对象相同,不同范围不同

        TestService1 test;
        using (ServiceProvider sp = serviceollection.BuildServiceProvider())
        {
            using (var scope1 = sp.CreateScope())
            {
                TestService1 t1 = scope1.ServiceProvider.GetService<TestService1>();

                TestService1 t2 = scope1.ServiceProvider.GetService<TestService1>();
                Console.WriteLine(object.ReferenceEquals(t1, t2));// t1和t2是同一个
                test = t1;
            }

            using (var scope2 = sp.CreateScope())
            {
                TestService1 t3 = scope2.ServiceProvider.GetService<TestService1>();
                TestService1 t4 = scope2.ServiceProvider.GetService<TestService1>();

                Console.WriteLine(object.ReferenceEquals(t3, t4));// 同一个对象
                Console.WriteLine(object.ReferenceEquals(test, t4));// 不是同一个
            }
        }
    }

在这里插入图片描述

  1. 如果一个类实现了IDisposable接口,则离开作用域之后容器会自动调用dispose方法。
  2. 不要在长生命周期的对象中引用比他生命周期短的对象。在asp.net core 中,这样做默认会抛异常。
  3. 生命周期的选择:如果类无状态,建议为Singleton;如果类有状态,且有scope控制,建议为scope的,因为通常scope控制下的代码都是在同一个线程中运行,没有并发修改问题;在使用transient的时候要谨慎。
  4. .net注册服务的重载方法很多,请自行看文档学习。

其他注册方法

  1. T GetService 如果获取不到对象,则返回null
    static void Main(string[] args)
    {
        ServiceCollection services = new ServiceCollection();
        services.AddScoped<ITestService, TestService1>(); // 更推荐这种写法 前面是服务类型,后面是实现类型
        using (ServiceProvider provider = services.BuildServiceProvider())
        {
            using (IServiceScope scope = provider.CreateScope())
            {
                // 这里必须和上面注册的服务类型一致 如果找不到则返回null
                ITestService testService = scope.ServiceProvider.GetService<ITestService>(); 
                 // 这里把ITestService换成TestService1, 会返回null 因为找不到注册的服务
               // ITestService testService = scope.ServiceProvider.GetService<ITestService>();
                
                Console.WriteLine(testService.GetType());
            }
        }
    }

在这里插入图片描述
2. object GetService(Type serviceType)

    static void Main(string[] args)
    {
        ServiceCollection services = new ServiceCollection();
        services.AddScoped<ITestService, TestService1>(); // 更推荐这种写法 前面是服务类型,后面是实现类型
        using (ServiceProvider provider = services.BuildServiceProvider())
        {
            using (IServiceScope scope = provider.CreateScope())
            {
                ITestService testService = (ITestService)scope.ServiceProvider.GetService(typeof(ITestService));

                Console.WriteLine(testService.GetType());
            }
        }
    }
  1. T GetRequiredSerice 如果获取不到对象,则抛异常
  2. object GetRequiredSerice(Type serviceType)
  3. IEnumerable GetServices 适用于可能有很多满足条件的服务
    static void Main(string[] args)
    {
        ServiceCollection services = new ServiceCollection();
        services.AddScoped<ITestService, TestService1>(); // 更推荐这种写法 前面是服务类型,后面是实现类型
        services.AddScoped<ITestService, TestService2>(); // 更推荐这种写法 前面是服务类型,后面是实现类型
        using (ServiceProvider provider = services.BuildServiceProvider())
        {
            using (IServiceScope scope = provider.CreateScope())
            {
                IEnumerable<ITestService> testServices = scope.ServiceProvider.GetServices<ITestService>();
                foreach (var item in testServices)
                {
                    Console.WriteLine(item.GetType());
                }

            }
        }
    }

在这里插入图片描述
这里有个需要注意的点:注册多个服务时,如果用GetRequiredSerice()去获取服务,只会获取到最新的。

  1. IEnumerable GetServices

2 依赖注入 DI

  1. 依赖注入是有传染性的,如果一个类的对象是通过DI创建的,那么这个类的构造函数中声明的所有服务类型的参数都会被DI赋值;但是如果一个对象是程序员手动创建的,那么这个对象和DI没有关系,它的构造函数中声明的服务类型参数就不会被自动赋值。
  2. .NET的DI默认是构造函数注入

例子:编写一个类,连接数据库做插入操作,并且记录服务日志(模拟输出),把Dao、日志都放入单独的服务类。

using Microsoft.Extensions.DependencyInjection;

internal class Program
{
    private static void Main(string[] args)
    {
        // 声明服务容器
        ServiceCollection services = new ServiceCollection();
        // 注册需要的服务
        services.AddScoped<ILog, Log>();
        services.AddScoped<IStorage, Strorage>();
        services.AddScoped<Controller>();
        services.AddScoped<IConfig, Config>();
        using (ServiceProvider provider = services.BuildServiceProvider())
        {
            // 获取服务
            Controller controller = provider.GetRequiredService<Controller>();
            controller.Test();
        }
    }

    class Controller
    {
        private readonly ILog log;
        private readonly IStorage storage;

        public Controller(ILog log, IStorage storage)
        {
            this.log = log;
            this.storage = storage;
        }

        public void Test()
        {
            this.log.WriteLog("开始写日志了");
            this.storage.Save("hahhaha", "file1");
            this.log.WriteLog("日志结束了");
        }
    }



    interface ILog
    {
        public void WriteLog(string message);
    }

    public class Log : ILog
    {
        public void WriteLog(string message)
        {
            Console.WriteLine(message);
        }
    }

    interface IConfig
    {
        public string GetValue();

    }

    public class Config : IConfig
    {
        public string GetValue()
        {
            return "server1";
        }
    }

    interface IStorage
    {
        public void Save(string content, string name);
    }

    class Strorage : IStorage
    {
        private readonly IConfig config;
        public Strorage(IConfig config)
        {
            this.config = config;
        }
        public void Save(string content, string name)
        {
            string server = config.GetValue();
            Console.WriteLine($"upload {content} to {server}, and its name is {name}");
        }
    }
}

在这里插入图片描述

案例

发送邮件

建三个类库,一个控制台程序。LogServices类库负责记录日志;MailServices类库负责发送邮件;ConfigServices类库负责获取配置信息;SendMailConsole控制台程序负责调用,发送邮件。
项目结构如下图所示:
在这里插入图片描述
IConfigService接口

using System;

namespace ConfigServices
{
    public interface IConfigService
    {
        public string GetValue(string name);
    }
}

ConfigService实现类

using System;
namespace ConfigServices
{
    public class ConfigService : IConfigService
    {
        public string GetValue(string name)
        {
            return name;
        }
    }
}

ILogService接口

using System;
namespace LogServices
{
    public interface ILogService
    {
        public void LogError();

        public void LogInfo(string message);
    }
}

LogService实现类

using System;

namespace LogServices
{
    public class LogService : ILogService
    {
        public void LogError()
        {
            Console.WriteLine("error");
        }

        public void LogInfo(string message)
        {
            Console.WriteLine($"info: {message}");
        }
    }
}

IMailService接口

using System;

namespace MailServices
{
    public interface IMailService
    {
        public void Send(string title, string recipients, string body);
    }
}

MailService实现类

using System;
using LogServices;
using ConfigServices;

namespace MailServices
{
    public class MailService : IMailService
    {
        private readonly ILogService logService;
        private readonly IConfigService configService;

        public MailService(ILogService logService, IConfigService configService)
        {
            this.logService = logService;
            this.configService = configService;
        }

        public void Send(string title, string recipients, string body)
        {
            this.logService.LogInfo("开始发送邮件了");
            string username = this.configService.GetValue("UserName");
            Console.WriteLine($"真的发送邮件了: {username}, {title}, {recipients}, {body}");
            this.logService.LogInfo("邮件发送完成");
        }
    }
}

SendMailConsole 控制台代码

using MailServices;
using LogServices;
using ConfigServices;

using Microsoft.Extensions.DependencyInjection;

namespace SendMailConsole;
class Program
{
    static void Main(string[] args)
    {
        ServiceCollection services = new ServiceCollection();
        services.AddScoped<IMailService, MailService>();
        services.AddScoped<ILogService, LogService>();
        services.AddScoped<IConfigService, ConfigService>();

        using (var provider = services.BuildServiceProvider())
        {
            IMailService mailService = provider.GetRequiredService<IMailService>();
            mailService.Send("案例一", "mzz", "imissyou");
        }

    }
}

在这里插入图片描述
这样的实现方式还是有一些不完美,因为在控制台程序中需要自己注册服务和实现类,这就要求你很清楚服务和实现类是谁,而我们期望的结果是可以直接用,不用很清楚细节。下面我们通过写扩展类的方法来修改一下上面的写法。
分别给上面的三个类库都增加一个扩展类,在扩展类里面注册服务,在控制台程序中直接调用这个扩展方法即可。

ConfigServiceExtension 扩展类

using System;
using ConfigServices;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class ConfigServiceExtension
    {
        public static void AddConfigService(this IServiceCollection services)
        {
            services.AddScoped<IConfigService, ConfigService>();
        }
    }
}

控制台程序代码

using MailServices;

using Microsoft.Extensions.DependencyInjection;

namespace SendMailConsole;
class Program
{
    static void Main(string[] args)
    {
        ServiceCollection services = new ServiceCollection();
        //services.AddScoped<IMailService, MailService>();
        //services.AddScoped<ILogService, LogService>();
        //services.AddScoped<IConfigService, ConfigService>();
        services.AddMailService();
        services.AddLogService();
        services.AddConfigService();

        using (var provider = services.BuildServiceProvider())
        {
            IMailService mailService = provider.GetRequiredService<IMailService>();
            mailService.Send("案例一", "mzz", "imissyou");
        }
    }
}

注意:因为我们希望扩展方法可以让ServiceCollection对象直接调用,所以扩展类的命名空间必须和ServiceCollection一致,即Microsoft.Extensions.DependencyInjection。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值