探索 .NET Core 依赖注入的 IServiceCollection

若是您使用了.NET Core,则极可能已使用Microsoft.Extensions.DependencyInjection中的内置依赖项注入容器,在本文中,我想更深刻地了解Microsoft Dependency Injection(DI)容器中的 IServiceCollection。安全

什么是依赖注入(DI)和DI容器?

Microsoft依赖项注入容器只是一组类,它们组合到一个代码库中,这个库会自动建立并管理程序中须要的对象。多线程

咱们先看下面的代码:app

public class ClassA
{
    public void DoWork() 
    {
        var b = new ClassB();
        b.DoStuff();
    }
}
 
public class ClassB
{
    public void DoStuff()
    {
        // ...
    }
}

ClassA直接依赖ClassB,而且在它的DoWork方法中,new了一个ClassB,而后调用了ClassB的DoStuff方法。框架

咱们改写一下代码看看:函数

public class ClassA
{
    private readonly ClassB _dependency;
 
    public ClassA(ClassB classB) => _dependency = classB;
 
    public void DoWork() => _dependency.DoStuff();
}
 
public class ClassB : IThing
{
    public void DoStuff()
    {
        // ...
    }
}

首先,我加了一个构造函数,而且指定了ClassA依赖的类型,调用构造函数时,必须提供ClassB的实例, 在ClassA的内部,咱们不会去new一个ClassB,ClassB彻底是由外部传入的,这里就是控制反转(IoC)。性能

进一步改进代码:ui

public interface IThing
{
    public void DoStuff();
}
 
public class ClassA
{
    private readonly IThing _dependency;
 
    public ClassA(IThing thing) => _dependency = thing;
 
    public void DoWork() => _dependency.DoStuff();
}
 
public class ClassB : IThing
{
    public void DoStuff()
    {
        // ...
    }
}

加了一个接口IThing,如今,咱们已经应用了SOLID的依赖倒置原则,咱们再也不依赖具体的实现,相反,咱们依赖于IThing抽象,在构造函数中,只须要传入IThing的实现就行了。this

而后在咱们的代码中,能够这样用:线程

class Program
{
    static void Main(string[] args)
    {
        IThing thing = new ClassB();
        ClassA classA = new ClassA(thing);
        classA.DoWork();
    }
}

咱们手动new了一个ClassB,它实现了IThing接口,而后建立ClassA的时候,直接把thing传入构造函数中。翻译

上面的代码演示,咱们只处理了ClassA和ClassB的依赖注入关系,可是在实际中呢,咱们代码中有不少类型,而后有各类各样的依赖关系。

这个时候咱们就须要一个DI容器,咱们对容器进行配置,然它知道什么类型,而后负责自动建立并管理对象(一般称为服务)。

注册服务

一般, Microsoft DI 容器须要在Startup类中配置,在这里,您可使用ConfigureServices方法向容器注册服务,在应用程序托管生命周期的早期,将调用ConfigureServices方法,它有一个参数IServiceCollection,这个参数在初始化应用程序时传入。

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // 注册服务
    }
 
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
    }
}

为了尽量的简单,咱们也能够在控制台中使用 Microsoft DependencyInjection。

建立控制台程序后,咱们首先在项目中引入Microsoft.Extensions.DependencyInjection

<Project Sdk="Microsoft.NET.Sdk">
 
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>
 
  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
  </ItemGroup>
 
</Project>

如今咱们开始注册咱们的服务,可是咱们须要一个IServiceCollection,让咱们看一下IServiceCollection的定义。

public interface IServiceCollection : IList<ServiceDescriptor>
{
}

IServiceCollection没有定义其任何成员,而是从IList<ServiceDescriptor>派生。

Microsoft.Extensions.DepenencyInjection程序包里面,它有一个默认的实现:ServiceCollection。

public class ServiceCollection : IServiceCollection
{
    private readonly List<ServiceDescriptor> _descriptors = new List<ServiceDescriptor>();
     
    public int Count => _descriptors.Count;
     
    public bool IsReadOnly => false;
     
    public ServiceDescriptor this[int index]
    {
        get
        {
            return _descriptors[index];
        }
        set
        {
            _descriptors[index] = value;
        }
    }
     
    // ...
}

它有一个私有的List集合:_descriptors,里面是ServiceDescriptor。

让咱们从建立一个ServiceCollection,而后注册两个服务。

static void Main(string[] args)
{
    var serviceCollection = new ServiceCollection();
 
    serviceCollection.AddSingleton<ClassA>();
    serviceCollection.AddSingleton<IThing, ClassB>();
 
    Console.WriteLine("Done");
}

在前面的代码中,咱们已经使用AddSingleton方法注册了两个服务,这不是IServiceCollection接口定义的方法,也不在ServiceCollection上,这是IServiceCollection的扩展方法,这个方法在ServiceCollectionServiceExtensions的扩展类中,接下来,我会介绍这个方法是如何注册服务的,不过这以前,咱们首先回顾下服务生命周期的概念。

服务生命周期

在Microsoft依赖项注入框架中,咱们可使用三种生命周期注册服务,分别是单例(Singleton)、瞬时(Transient)、做用域(Scoped),在上面的代码中,我使用了AddSingleton()来注册服务。

使用Singleton服务的优势是咱们不会建立多个服务实例,只会建立一个实例,保存到DI容器中,直到程序退出,这不只效率高,并且性能高,可是有一个要注意的点,若是在多线程中使用了Singleton,要考虑线程安全的问题,保证它不会有冲突。

瞬时(Transient)和单例(Singleton)模式是相反的,每次使用时,DI容器都是建立一个新的实例。

做用域(Scoped),在一个做用域内,会使用同一个实例,像EF Core的DbContext上下文就被注册为做用域服务。

咱们注册服务时会发生什么?

在上面的代码中,我已经注册了两个单例服务。

serviceCollection.AddSingleton<ClassA>();
serviceCollection.AddSingleton<IThing, ClassB>();

这是最终的AddSingleton方法:

public static IServiceCollection AddSingleton(
    this IServiceCollection services,
    Type serviceType,
    Type implementationType)
{
    if (services == null)
    {
        throw new ArgumentNullException(nameof(services));
    }
    if (serviceType == null)
    {
        throw new ArgumentNullException(nameof(serviceType));
    }
    if (implementationType == null)
    {
        throw new ArgumentNullException(nameof(implementationType));
    }
    return Add(services, serviceType, implementationType, ServiceLifetime.Singleton);
}

咱们能够看到AddSingleton方法调用了私有的Add方法,而且传入了一个生命周期的枚举值ServiceLifetime.Singleton

让咱们看一下Add方法的工做原理:

private static IServiceCollection Add(
    IServiceCollection collection,
    Type serviceType,
    Type implementationType,
    ServiceLifetime lifetime)
{
    var descriptor = new ServiceDescriptor(serviceType, implementationType, lifetime);
    collection.Add(descriptor);
    return collection;
}

它建立一个新的ServiceDescriptor实例,传入服务类型,实现类型(可能与服务类型相同)和生命周期,而后调用Add方法添加到列表中。

以前,咱们了解到IServiceCollection本质上是包装了List <ServiceDescriptor>, ServiceDescriptor类很简单,表明一个注册的服务,包括其服务类型,实现类型和生命周期。

实例注册

咱们也能够手动new一个实例,而后传入到AddSingleton()方法中:

var myInstance = new ClassB();
serviceCollection.AddSingleton<IThing>(myInstance);

使用 ServiceDescriptor

咱们还能够手动定义一个ServiceDescriptor,而后直接添加到IServiceCollection中。

var descriptor = new ServiceDescriptor(typeof(IThing), typeof(ClassB), ServiceLifetime.Singleton);
serviceCollection.Add(descriptor);

总结

在本文中,介绍了.NET中的DI的一些核心知识,能够直接建立ServiceCollection来使用Microsoft DI框架,了解了IServiceCollection上的AddSingleton扩展方法是如何工做,以及它们最终建立了一个ServiceDescriptor,而后添加到一个ServiceCollection包装的List集合中。

原文连接: https://www.stevejgordon.co.uk/aspnet-core-dependency-injection-what-is-the-iservicecollection

最后

欢迎扫码关注咱们的公众号 【全球技术精选】,专一国外优秀博客的翻译和开源项目分享,也能够添加QQ群 897216102

相关文章
相关标签/搜索