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

目录
一、ServiceDescriptor

二、IServiceCollection
     Add
     Add{Lifetime}
     TryAdd
     TryAdd{Lifetime}
     TryAddEnumerable
     RemoveAll & Replace

一、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'.”


图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)));

依赖注入[1]: 控制反转
依赖注入[2]: 基于IoC的设计模式
依赖注入[3]: 依赖注入模式
依赖注入[4]: 创建一个简易版的DI框架[上篇]
依赖注入[5]: 创建一个简易版的DI框架[下篇]
依赖注入[6]: .NET Core DI框架[编程体验]
依赖注入[7]: .NET Core DI框架[服务注册]
依赖注入[8]: .NET Core DI框架[服务消费]

依赖注入[7]: .NET Core DI框架[服务注册]的更多相关文章

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

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

  2. 依赖注入[8]: .NET Core DI框架[服务消费]

    包含服务注册信息的IServiceCollection对象最终被用来创建作为DI容器的IServiceProvider对象.当需要消费某个服务实例的时候,我们只需要指定服务类型调用IServicePr ...

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

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

  4. 依赖注入[6]: .NET Core DI框架[编程体验]

    毫不夸张地说,整个ASP.NET Core框架是建立在一个依赖注入框架之上的,它在应用启动时构建请求处理管道过程中,以及利用该管道处理每个请求过程中使用到的服务对象均来源于DI容器.该DI容器不仅为A ...

  5. .NET CORE学习笔记系列(2)——依赖注入[6]: .NET Core DI框架[编程体验]

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

  6. ASP.NET Core依赖注入系统学习教程:关于服务注册使用到的方法

    在.NET Core的依赖注入框架中,服务注册的信息将会被封装成ServiceDescriptor对象,而这些对象都会存储在IServiceCollection接口类型表示的集合中,另外,IServi ...

  7. 浅谈(IOC)依赖注入与控制反转(DI)

    前言:参考了百度文献和https://www.cnblogs.com/liuqifeng/p/11077592.html以及http://www.cnblogs.com/leoo2sk/archive ...

  8. 依赖注入在 dotnet core 中实现与使用:2 使用 Extensions DependencyInjection

    既然是依赖注入容器,必然会涉及到服务的注册,获取服务实例,管理作用域,服务注入这四个方面. 服务注册涉及如何将我们的定义的服务注册到容器中.这通常是实际开发中使用容器的第一步,而容器本身通常是由框架来 ...

  9. ASP.NET Core MVC 2.x 全面教程_ASP.NET Core MVC 03. 服务注册和管道

    ASP.NET Core MVC 2.x 全面教程_ASP.NET Core MVC 03. 服务注册和管道 语雀: https://www.yuque.com/yuejiangliu/dotnet/ ...

随机推荐

  1. 使用 DG Tweening

    在iphone上卡顿的话,使用application.frame更改刷新帧率

  2. oracle Data Modeler 使用教程

    由于 powerdesigner 的版权问题.公司要求集体换成 oracle Data Modeler .免费版就够用,哈哈.这有很详细的入门教程,看一看吧: 官方正版教程 ,特详细,只是英文的,也只 ...

  3. ArrayList源码学习

    1.ArrayList:基于数据实现,允许出现空值和重复元素,当ArrayList中添加的元素数量大于底层数组容量是,会通过扩容机制重新生成一个更大的数组.(非线程安全) 2.源码分析 构造函数 /* ...

  4. 使用 Vscode +PlantUml 画uml图

    什么是PlantUML PlantUML是一个快速创建UML图形的组件,官网上之所以称它是一个组件,主要是因为多数情况下我们都是在Eclipse.NetBenas.Intellijidea. Emac ...

  5. 实例分析ASP.NET在MVC5中使用MiniProfiler监控MVC性能的方法 

    这篇文章主要为大家详细介绍了ASP.NET MVC5使用MiniProfiler监控MVC性能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 MiniProfiler ,一个简单而有效的迷你剖析器 ...

  6. docker异常问题解决

    解决方法: 发现这个问题出现的时候,并不是所有的docker都会出现,只影响某个docker 停下:docker stop app-6019-bonus 再起来:docker start app-60 ...

  7. 微软开源大规模数据处理项目 Data Accelerator

    微软开源了一个原为内部使用的大规模数据处理项目 Data Accelerator.自 2017 年开发以来,该项目已经大规模应用在各种微软产品工作管道上. 据微软官方开源博客介绍,Data Accel ...

  8. x64类型的程序逆向思考

    x64类型比较习惯ida去分析,需要注意的是在x64程序中,有时会因为自己对寄存器不太熟悉导致自己分析过程混淆,下面坐下简单记录

  9. 动态规划——Russian Doll Envelopes

    这个题大意很好理解,通过例子就能明白,很像俄罗斯套娃,大的娃娃套小的娃娃.这个题是大信封套小信封,每个信封都有长和宽,如果A信封的长和宽都要比B信封的要大,那么A信封可以套B信封,现在给定一组信封的大 ...

  10. MySQL索引背后的数据结构及算法原理(转)

    转自:http://blog.codinglabs.org/articles/theory-of-mysql-index.html 摘要 本文以MySQL数据库为研究对象,讨论与数据库索引相关的一些话 ...