.NET CORE学习笔记系列(2)——依赖注入[7]: .NET Core DI框架[服务注册]

时间:2022-09-02 10:30:14

原文https://www.cnblogs.com/artech/p/net-core-di-07.html

包含服务注册信息的IServiceCollection对象最终被用来创建作为DI容器的IServiceProvider对象。服务注册就是创建出现相应的ServiceDescriptor对象并将其添加到指定IServiceCollection集合对象中的过程。

一、ServiceDescriptor

通过《依赖注入[6]: .NET Core DI编程体验》的实例演示我们知道作为DI容器的IServiceProvider对象是通过调用IServiceCollection接口的扩展方法BuildServiceProvider创建的,IServiceCollection对象是一个存放服务注册信息的集合。Cat中的服务注册是通过一个类型为ServiceRegistry的对象表示的,在IServiceCollection/IServiceProvider为核心的DI框架中,与之对应的类型为ServiceDescriptor。

DI框架将服务注册存储在一个通过IServiceCollection接口表示的集合之中。如下面的代码片段所示,一个IServiceCollection对象本质上就是一个元素类型为ServiceDescriptor的列表。在默认情况下我们使用的是实现该接口的ServiceCollection类型。

public class ServiceDescriptor
{
    public Type                 ServiceType { get; }
    public ServiceLifetime             Lifetime { get; }

    public Type                 ImplementationType { get; }
    public Func<IServiceProvider, object>     ImplementationFactory { get; }
    public object                 ImplementationInstance { get; }

    public ServiceDescriptor(Type serviceType, object instance);
    public ServiceDescriptor(Type serviceType, Func<IServiceProvider, object> factory, ServiceLifetime lifetime);
    public ServiceDescriptor(Type serviceType, Type implementationType, ServiceLifetime lifetime);
}

ServiceDescriptor的其他三个属性体现了服务实例的三种提供方式,并对应着三个构造函数。如果我们指定了服务的实现类型(对应于ImplementationType属性),那么最终的服务实例将通过调用定义在实现类型中某一个构造函数来创建。如果指定的是一个Func<IServiceProvider, object>对象(对应于ImplementationFactory属性),那么IServiceProvider对象将会将自身作为输入参数调用该委托对象来提供服务实例。如果我们直接指定一个现有的对象(对应的属性为ImplementationInstance),那么该对象就是最终提供的服务实例。

如果我们采用直接提供服务实例的形式来创建ServiceDescriptor对象,意味着服务注册默认采用Singleton生命周期模式。对于通过其他两个构造函数创建创建的ServiceDescriptor对象来说,则需要显式指定采用的生命周期模式。相较于ServiceDescriptor,我们在Cat框架中定义的ServiceRegistry显得更加精炼,因为我们直接提供了一个类型为Func<Cat,Type[], object>的属性来提供对应的服务实例。

除了调用上面介绍的三个构造函数来创建对应的ServiceDescriptor对象之外,我们还可以提供定义在ServiceDescriptor类型中一系列静态方法来创建该对象。如下面的代码片段所示,ServiceDescriptor提供了如下两个名为Describe的方法重载来创建对应的ServiceDescriptor对象。

public class ServiceDescriptor
{
    public static ServiceDescriptor Describe(Type serviceType, Func<IServiceProvider, object> implementationFactory, ServiceLifetime lifetime);
    public static ServiceDescriptor Describe(Type serviceType, Type implementationType, ServiceLifetime lifetime);
}

当我们调用上面两个Describe方法来创建ServiceDescriptor对象的时候总是需要指定采用的生命周期模式,为了让对象创建变得更加简单,ServiceDescriptor中还定义了一系列针对三种生命周期模式的静态工厂方法。如下所示的是针对Singleton模式的一组Singleton方法重载的定义,针对其他两种模式的Scoped和Transient方法具有类似的定义。

public class ServiceDescriptor
{
    public static ServiceDescriptor Singleton<TService, TImplementation>() where TService: class where TImplementation: class, TService;
    public static ServiceDescriptor Singleton<TService, TImplementation>(Func<IServiceProvider, TImplementation> implementationFactory) where TService: class where TImplementation: class, TService;
    public static ServiceDescriptor Singleton<TService>(Func<IServiceProvider, TService> implementationFactory) where TService: class;
    public static ServiceDescriptor Singleton<TService>(TService implementationInstance) where TService: class;
    public static ServiceDescriptor Singleton(Type serviceType, Func<IServiceProvider, object> implementationFactory);
    public static ServiceDescriptor Singleton(Type serviceType, object implementationInstance);
    public static ServiceDescriptor Singleton(Type service, Type implementationType);
}

二、IServiceCollection

DI框架将服务注册存储在一个通过IServiceCollection接口表示的集合之中。如下面的代码片段所示,一个IServiceCollection对象本质上就是一个元素类型为ServiceDescriptor的列表。在默认情况下我们使用的是实现该接口的ServiceCollection类型。

public interface IServiceCollection : IList<ServiceDescriptor>
{}
public class ServiceCollection : IServiceCollection
{}

Add

我们在应用启动的时候所做的服务注册就是创建出现相应的ServiceDescriptor对象并将其添加到指定IServiceCollection集合对象中的过程。考虑到服务注册是一个高频调用的操作,所以DI框架为IServiceCollection接口定义了一系列扩展方法完成服务注册的工作,比如下面的这两个Add方法可以将指定的一个或者多个ServiceDescriptor对象添加到IServiceCollection集合中。

public static class ServiceCollectionDescriptorExtensions
{
    public static IServiceCollection Add(this IServiceCollection collection, ServiceDescriptor descriptor);
    public static IServiceCollection Add(this IServiceCollection collection, IEnumerable<ServiceDescriptor> descriptors);
}

Add{Lifetime}

DI框架还针对具体生命周期模式为IServiceCollection接口定义了一系列的扩展方法,它们会根据提供的输入创建出对应的ServiceDescriptor对象并将其添加到指定的IServiceCollection对象中。如下所示的是针对Singleton模式的AddSingleton方法重载的定义,针对其他两个生命周期模式的AddScoped和AddTransient方法具有类似的定义。

public static class ServiceCollectionServiceExtensions
{
    public static IServiceCollection AddSingleton<TService>(this IServiceCollection services) where TService: class;
    public static IServiceCollection AddSingleton<TService, TImplementation>(this IServiceCollection services) where TService: class where TImplementation: class, TService;
    public static IServiceCollection AddSingleton<TService>(this IServiceCollection services, TService implementationInstance)  where TService: class;
    public static IServiceCollection AddSingleton<TService, TImplementation>(this IServiceCollection services, Func<IServiceProvider, TImplementation> implementationFactory)  where TService: class where TImplementation: class, TService;
    public static IServiceCollection AddSingleton<TService>(this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory)  where TService: class;
    public static IServiceCollection AddSingleton(this IServiceCollection services, Type serviceType);
    public static IServiceCollection AddSingleton(this IServiceCollection services, Type serviceType, Func<IServiceProvider, object> implementationFactory);
    public static IServiceCollection AddSingleton(this IServiceCollection services, Type serviceType, object implementationInstance);
    public static IServiceCollection AddSingleton(this IServiceCollection services, Type serviceType, Type implementationType);
}

TryAdd

虽然针对同一个服务类型可以添加多个ServiceDescriptor,但这情况只有在应用需要使用到同一类型的多个服务实例的情况下才有意义,比如我们可以注册多个ServiceDescriptor来提供同一个主题的多个订阅者。如果我们总是根据指定的服务类型来提取单一的服务实例,这种情况下一个服务类型只需要一个ServiceDescriptor对象就够了。对于这种场景我们可能会使用如下两个名为TryAdd的扩展方法,该方法会根据指定ServiceDescriptor提供的服务类型判断对应的服务注册是否存在,只有不存在指定类型的服务注册情况下,我们提供的ServiceDescriptor才会被添加到指定的IServiceCollection对象中。

public static class ServiceCollectionDescriptorExtensions
{
    public static void TryAdd(this IServiceCollection collection, ServiceDescriptor descriptor);
    public static void TryAdd(this IServiceCollection collection, IEnumerable<ServiceDescriptor> descriptors);
}

TryAdd{Lifetime}

扩展方法TryAdd同样具有基于三种生命周期模式的版本,如下所示的针对Singleton模式的TryAddSingleton方法的定义。在指定服务类型对应的ServiceDescriptor不存在的情况下,它们会采用提供的实现类型、服务实例创建工厂以及服务实例来创建生命周期模式为Singleton的ServiceDescriptor对象并将其添加到指定的IServiceCollection对象中。针对其他两种生命周期模式的TryAddScoped和TryAddTransient方法具有类似的定义。

public static class ServiceCollectionDescriptorExtensions
{
    public static void TryAddSingleton<TService>(this IServiceCollection collection)  where TService: class;
    public static void TryAddSingleton<TService, TImplementation>(this IServiceCollection collection)  where TService: class  where TImplementation: class, TService;
    public static void TryAddSingleton(this IServiceCollection collection,  Type service);
    public static void TryAddSingleton<TService>(this IServiceCollection collection,  TService instance) where TService: class;
    public static void TryAddSingleton<TService>(this IServiceCollection services,  Func<IServiceProvider, TService> implementationFactory)  where TService: class;
    public static void TryAddSingleton(this IServiceCollection collection,  Type service, Func<IServiceProvider, object> implementationFactory);
    public static void TryAddSingleton(this IServiceCollection collection,  Type service, Type implementationType);
}

TryAddEnumerable

除了上面介绍的扩展方法TryAdd和TryAdd{Lifetime}之外,IServiceCollection接口还具有如下两个名为TryAddEnumerable的扩展方法。当TryAddEnumerable方法在决定将指定的ServiceDescriptor添加到IServiceCollection对象之前,它也会做存在性检验。与TryAdd和TryAdd{Lifetime}方法不同的是,该方法在判断执行的ServiceDescriptor是否存在是会同时考虑服务类型和实现类型。

public static class ServiceCollectionDescriptorExtensions
{
    public static void TryAddEnumerable(this IServiceCollection services, ServiceDescriptor descriptor);
    public static void TryAddEnumerable(this IServiceCollection services, IEnumerable<ServiceDescriptor> descriptors);
}

被TryAddEnumerable方法用来判断存在性的实现类型不只是ServiceDescriptor的ImplementationType属性。如果ServiceDescriptor是通过一个指定的服务实例创建的,那么该实例的类型会作为用来判断存在与否的实现类型。如果ServiceDescriptor是通过提供的服务实例工厂来创建的,那么代表服务实例创建工厂的Func<in T, out TResult>对象的第二个参数类型将被用于判断ServiceDescriptor的存在性。扩张方法TryAddEnumerable的实现逻辑可言通过如下这段程序来验证。

var services = new ServiceCollection();

services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux, Foo>());
Debug.Assert(services.Count == 1);
services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux, Foo>());
Debug.Assert(services.Count == 1);
services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux>(new Foo()));
Debug.Assert(services.Count == 1);
Func<IServiceProvider, Foo> factory4Foo = _ => new Foo();
services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux>(factory4Foo));
Debug.Assert(services.Count == 1);

services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux, Bar>());
Debug.Assert(services.Count == 2);
services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux>(new Baz()));
Debug.Assert(services.Count == 3);
Func<IServiceProvider, Gux> factory4Gux = _ => new Gux();
services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux>(factory4Gux));
Debug.Assert(services.Count == 4);

如果通过上述策略得到的实现类型为Object,那么TryAddEnumerable会因为实现类型不明确而抛出一个ArgumentException类型的异常。这种情况主要发生在提供的ServiceDescriptor对象是由服务实例工厂创建的情况,所以上面实例中用来创建ServiceDescriptor的工厂类型分别为Func<IServiceProvider, Foo>和Func<IServiceProvider, Gux>,而不是Func<IServiceProvider, object>。

var service = ServiceDescriptor.Singleton<IFoobarbazgux>(_ => new Foo());
new ServiceCollection().TryAddEnumerable(service);

假设我们采用如上所示的方式利用一个Lamda表达式来创建一个ServiceDescriptor对象,对于创建的ServiceDescriptor来说,其服务实例工厂是一个Func<IServiceProvider, object>对象,所以当我们将它作为参数调用TryAddEnumerable方法的会抛出如图1所示的ArgumentException异常,并提示“Implementation type cannot be 'App.IFoobarbazgux' because it is indistinguishable from other services registered for 'App.IFoobarbazgux'.”

.NET CORE学习笔记系列(2)——依赖注入[7]: .NET Core DI框架[服务注册]
图1实现类型不明确导致的异常

RemoveAll & Replace

上面介绍的这些方法最终的目的都是添加新的ServiceDescriptor到指定的IServiceCollection对象中,有的时候我们还希望删除或者替换现有的某个ServiceDescriptor,这种情况下通常发生在需要对当前使用框架中由某个服务提供的功能进行定制的时候。由于IServiceCollection实现了IList<ServiceDescriptor>接口,所以我们可以调用其Clear、Remove和RemoveAt方法来清除或者删除现有的ServiceDescriptor。除此之外,我们还可以选择如下这些扩展方法。

public static class ServiceCollectionDescriptorExtensions
{
    public static IServiceCollection RemoveAll<T>( this IServiceCollection collection);
    public static IServiceCollection RemoveAll(this IServiceCollection collection,  Type serviceType);
    public static IServiceCollection Replace(this IServiceCollection collection,  ServiceDescriptor descriptor);
}

RemoveAll和RemoveAll<T>方法帮助我们针对指定的服务类型来删除添加的ServiceDescriptor。Replace方法会使用指定的ServiceDescriptor去替换第一个具有相同服务类型(对应ServiceType属性)的ServiceDescriptor,实际操作是先删除后添加。如果从目前的IServiceCollection中找不到服务类型匹配的ServiceDescriptor,指定的ServiceDescriptor会直接添加到IServiceCollection对象中,这一逻辑也可以利用如下的程序来验证。

var services = new ServiceCollection();
services.Replace(ServiceDescriptor.Singleton<IFoobarbazgux, Foo>());
Debug.Assert(services.Any(it => it.ImplementationType == typeof(Foo)));

services.AddSingleton<IFoobarbazgux, Bar>();
services.Replace(ServiceDescriptor.Singleton<IFoobarbazgux, Baz>());
Debug.Assert(!services.Any(it=>it.ImplementationType == typeof(Foo)));
Debug.Assert(services.Any(it => it.ImplementationType == typeof(Bar)));
Debug.Assert(services.Any(it => it.ImplementationType == typeof(Baz)));

  下一篇.NET CORE学习笔记系列(2)——依赖注入[8]: .NET Core DI框架[服务消费]

.NET CORE学习笔记系列(2)——依赖注入[7]: .NET Core DI框架[服务注册]的更多相关文章

  1. &period;NET CORE学习笔记系列&lpar;2&rpar;——依赖注入&lbrack;6&rsqb;&colon; &period;NET Core DI框架&lbrack;编程体验&rsqb;

    原文https://www.cnblogs.com/artech/p/net-core-di-06.html 毫不夸张地说,整个ASP.NET Core框架是建立在一个依赖注入框架之上的,它在应用启动 ...

  2. &period;NET CORE学习笔记系列&lpar;2&rpar;——依赖注入&lbrack;8&rsqb;&colon; &period;NET Core DI框架&lbrack;服务消费&rsqb;

    原文:https://www.cnblogs.com/artech/p/net-core-di-08.html 包含服务注册信息的IServiceCollection对象最终被用来创建作为DI容器的I ...

  3. &period;NET CORE学习笔记系列&lpar;2&rpar;——依赖注入&lbrack;5&rsqb;&colon; 创建一个简易版的DI框架&lbrack;下篇&rsqb;

    为了让读者朋友们能够对.NET Core DI框架的实现原理具有一个深刻而认识,我们采用与之类似的设计构架了一个名为Cat的DI框架.在上篇中我们介绍了Cat的基本编程模式,接下来我们就来聊聊Cat的 ...

  4. &period;NET CORE学习笔记系列&lpar;2&rpar;——依赖注入&lbrack;4&rsqb;&colon; 创建一个简易版的DI框架&lbrack;上篇&rsqb;

    原文https://www.cnblogs.com/artech/p/net-core-di-04.html 本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章从 ...

  5. &period;NET CORE学习笔记系列&lpar;2&rpar;——依赖注入【3】依赖注入模式

    原文:https://www.cnblogs.com/artech/p/net-core-di-03.html IoC主要体现了这样一种设计思想:通过将一组通用流程的控制权从应用转移到框架中以实现对流 ...

  6. &period;NET CORE学习笔记系列&lpar;2&rpar;——依赖注入【2】基于IoC的设计模式

    原文:https://www.cnblogs.com/artech/p/net-core-di-02.html 正如我们在<控制反转>提到过的,很多人将IoC理解为一种“面向对象的设计模式 ...

  7. &period;NET CORE学习笔记系列&lpar;2&rpar;——依赖注入【1】控制反转IOC

    原文:https://www.cnblogs.com/artech/p/net-core-di-01.html 一.流程控制的反转 IoC的全名Inverse of Control,翻译成中文就是“控 ...

  8. ASP&period;NET Core 学习笔记 第二篇 依赖注入

    前言 ASP.NET Core 应用在启动过程中会依赖各种组件提供服务,而这些组件会以接口的形式标准化,这些组件这就是我们所说的服务,ASP.NET Core框架建立在一个底层的依赖注入框架之上,它使 ...

  9. 依赖注入&lbrack;7&rsqb;&colon; &period;NET Core DI框架&lbrack;服务注册&rsqb;

    包含服务注册信息的IServiceCollection对象最终被用来创建作为DI容器的IServiceProvider对象.服务注册就是创建出现相应的ServiceDescriptor对象并将其添加到 ...

随机推荐

  1. Android锁屏或灭屏状态下,快速按两次音量下键实现抓拍功能(1&period;2Framework层使用startService形式实现)

        如前一篇博文所分析,我们可以使用广播的形式在快速按下两次音量下键的时候发出广播,以方便客户端进行捕捉.既然有两种方式可以实现该Issue那么哪种方式是首选呢?     我个人推荐使用启动服务的 ...

  2. JSP取得绝对路径

    在JavaWeb开发中,常使用绝对路径的方式来引入JavaScript和CSS文件,这样可以避免因为目录变动导致引入文件找不到的情况,常用的做法如下: 一.使用${pageContext.reques ...

  3. MySQL关闭过程详解和安全关闭MySQL的方法

    MySQL关闭过程详解和安全关闭MySQL的方法 www.hongkevip.com 时间: -- : 阅读: 整理: 红客VIP 分享到: 红客VIP(http://www.hongkevip.co ...

  4. c&plus;&plus; ,类型转换

    一.隐式转换 1)精度低转高,sigend转unsigend2)数值0,会转为为指针. 数组名会转换为首地址.3)bool转换 .0为false.其他为true.-1也是true...4)非const ...

  5. Mustache&period;js使用笔记(内容属于转载总结)

    1.Mustache的语法很简单,用两个大括号标记要绑定的字段即可,“{{}}” Mustache主要的渲染方法为Mustache.render(),该方法有两个参数,第一个为要渲染的模版, 也就是例 ...

  6. php&lowbar;curl&period;dll libssh2&period;dll 始终无法加载的原因 及解决办法

    在*得到最终原因及解决办法 http://*.com/questions/16424117/php-unable-to-load-php-curl-dl ...

  7. Yii2 验证码

    没有用默认的layout  验证码点击不会改变 下面是静态页面代码 <div class="col-sm-12 login"> <div class=" ...

  8. corejava-chap01

    <java是什么:>Programming language 程序语言Development environment 开发环境Application environment 应用环境Dep ...

  9. C&num; 使用 SmtpClient 发送邮件注意项

    最近有邮件发送需求,使用 C#  SmtpClient 对象发送邮件 , 报异常, 如下错误代码: 调整代码顺序后,发送邮件成功! 注意:一定要先设置 EnableSsl和UseDefaultCred ...

  10. 工作中使用case用法小结

    五证合一sql语法解决办法 工作的时候,数据库里面存储某张表里面证件号码存储在不同的字段下面,然后前台需要写一个查询语句,根据数据库里面存储证件号码查询该条数据. 实际情况如下 有些部门上传数据,不是 ...