Alva

Alva

programmer
github
telegram
email

dotnet 中的依赖注入-05.注册方法

本文示例代码,均采用 .NET 6,具体的代码可以在这个仓库 Articles.DI 中获取。

通过前面的文章,了解到了服务的三种声明周期。那么如果我们需要注册服务时,框架都提供了哪些 API 呢?当我们要根据自身需求来声明服务,声明具体的实现时,又该如何编写代码呢?本文将探讨 .NET 内置的 DI 框架提供的 API,以及其使用方法,具体作用。

.NET API 浏览器,提供了详细的 API 文档。这个链接就展示了注册服务相关的 API。

注册方法#

注册服务的方法,肯定不止Add{LifeTime}<TService, TImplementation>()这一个方法,具体的方法类型,可以见点击这个链接,查看文档提供的表格,我在下面直接将其粘贴出来。

方法自动释放多种实现传递参数
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>()
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION})
Add{LIFETIME}<{IMPLEMENTATION}>()
AddSingleton<{SERVICE}>(new {IMPLEMENTATION})
AddSingleton(new {IMPLEMENTATION})

上面的表格,只列出了支持泛型的方法,还有形如Add{LIFETIME}(typeof({SERVICE}), typeof({IMPLEMENTATION}))的方法,其本质和泛型方法一样,这里就不列出来了。表格中右边三列,分别描述了这些方法的局限范围。

服务释放#

容器负责服务的构建,但是如果一个服务到期,又是谁来负责服务的释放?是服务的依赖者?还是容器?

前面表格中的自动释放一列,如果为,则表明通过这类方法注册的服务,由容器统一进行服务的释放,如果这些服务中有实现IDisposable接口,那么也由容器来自动调用Dispose方法,无需在代码中显式调用释放方法。

查看表格,可以看到AddSingleton<{SERVICE}>(new {IMPLEMENTATION})AddSingleton(new {IMPLEMENTATION})两类方法中,是不会自动释放的,容器框架不会去自行释放,那么就需要开发人员自行负责服务的释放。

多种实现#

前面的例子中,我们注册时都是一个接口,一个实现类,在依赖函数注入时,注入的都是单个服务。如果我们想要将多个实现,都注册为同一种接口,又该使用哪些方法呢?

前面表格中可以看到在多种实现这一列,有两类方法不支持多种实现,Add{LIFETIME}<{IMPLEMENTATION}>()AddSingleton(new {IMPLEMENTATION})。其实查看方法描述就可以了解到,前者注册时,直接将IMPLEMENTATION类的实现,注册为IMPLEMENTATION类型的服务,没法做到多种实现。而后者也是类似,直接将一个注册实例,自然也没法做到多种实现

这里的多种实现,指的是实现了同一种接口的多个实现类,在注册服务的时候,同时注册到这个接口上。在上一篇文章中,我们是用一个实现类实现了多种接口,在注册服务的时候,这个类同时注册了多个接口,这两种情况是不一样的,后者不算多种实现

接下来我们直接使用代码,来展示如何做到多种实现

// https://github.com/alva-lin/Articles.DI/tree/master/WorkerService4
// 1. 设定一个接口 IMyDependency,以及实现这个接口的多个实现类
public interface IMyDependency
{
    string Id { get; }

    bool Flag { get; }
}

public class MyDependency0 : IMyDependency
{
    public string Id { get; } = Guid.NewGuid().ToString()[^4..];

    public bool Flag { get; init; } = false;
}

public class MyDependency1 : MyDependency0 {}
public class MyDependency2 : MyDependency0 {}
public class MyDependency3 : MyDependency0 {}
public class MyDependency4 : MyDependency0 {}
// 2. 注册服务
using Microsoft.Extensions.DependencyInjection.Extensions;

IHost host = Host.CreateDefaultBuilder(args)
   .ConfigureServices(services =>
    {
        // 同时注册一个接口的多种实现
        services.AddTransient<IMyDependency, MyDependency1>();
        services.AddTransient<IMyDependency, MyDependency2>();

        // TryAdd 如果接口已被注册过,不会再注册
        // IMyDependency 已经被注册过,所以并不会将 MyDependency3 注册为它的实现
        services.TryAddTransient<IMyDependency, MyDependency3>();

        // TryAddEnumerable 如果接口的同一种实现被注册过,则不会重复注册
        // IMyDependency -> MyDependency4,这个关系没有被注册过,所以可以成功注册
        var descriptor = new ServiceDescriptor(
            typeof(IMyDependency),
            typeof(MyDependency4),
            ServiceLifetime.Transient);
        services.TryAddEnumerable(descriptor);

        // 不会检查是否注册过这个接口,也不会检查是否注册过这个关系,会重复注册
        // MyDependency2 被注册过两次
        // 那么在获取 IEnumerable<IMyDependency> 时
        // MyDependency2 类型的实例会有两个
        services.AddTransient<IMyDependency, MyDependency2>(_ => new MyDependency2
        {
            Flag = true // 单独使用工厂方法构造这个服务,用于区分最后一次注册的服务
        });
    })
   .Build();

Fun(host.Services);

static void Fun(IServiceProvider serviceProvider)
{
    using var scopeServices = serviceProvider.CreateScope();
    var       services      = scopeServices.ServiceProvider;

    var myDependency = services.GetRequiredService<IMyDependency>();
    GetData(myDependency);
    Console.WriteLine();

    var list = services.GetRequiredService<IEnumerable<IMyDependency>>();
    foreach (var dependency in list)
    {
        GetData(dependency);
    }
    Console.WriteLine();
}

static void GetData<T>(T item) where T : IMyDependency
{
    Console.WriteLine($"{item.GetType().Name} {item.Id}, {item.Flag}");
}

程序的运行结果如下:

MyDependency2 c432, True

MyDependency1 ea48, False
MyDependency2 9b9a, False
MyDependency4 c4ce, False
MyDependency2 77e9, True

查看整个程序的输出结果,因为将所有服务注册为Transient类型的声明周期,所以每个实例的 Id 都不一样。

查看第一行输出中,item.Flag 为True,也就是说在解析IMyDependency类型时,最后一次成功注册的实现类将会被构造并注入(即本例中的myDependency变量)。

对比下面四行输出,其顺序正好就是服务注册时的顺序,即获取IEnumerable<IMyDependency>类型的变量时,会按照注册顺序,将所有成功注册的类型,统统注入进去。而这里面的第二行和第四行,都是MyDependency2,说明注册服务,是可以重复注册同一实现的。

传递参数#

方法自动释放多种实现传递参数
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>()
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION})
Add{LIFETIME}<{IMPLEMENTATION}>()
AddSingleton<{SERVICE}>(new {IMPLEMENTATION})
AddSingleton(new {IMPLEMENTATION})

传递参数其实就是容器在构造服务实例时,是否可以使用工厂方法,使用多样化的方式构造实例,而非单纯调用构造函数。查看这个表格,若是传递了工厂方法的,其传递参数列都为,反之则为。实际上,即使不传递工厂方法,也可以用其他途径,做到传递参数的目的。

比如在构造函数中使用IOperation<>类型。将需要的配置数据,注册到IOperation<MyOperation>中,就可以在构造函数中,拿到需要的实时数据,从而达到传递参数的目的。而具体配置数据的获取,则可以放在IOperation<MyOperation>的注册方法中。

参考链接#

.NET 中的依赖关系注入

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。