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

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

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

我们先看下面的代码:

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)。

进一步改进代码:

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的实现就好了。

然后在我们的代码中,可以这样用:

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

探索 .NET Core 依赖注入的 IServiceCollection的更多相关文章

  1. 探索 .NET Core 依赖注入的 IServiceProvider

    在上一篇文章中,我们学习了Microsoft.Extensions.DependencyInjection中的IServiceCollection,包括服务注册转换为ServiceDescriptor ...

  2. Webservice WCF WebApi 前端数据可视化 前端数据可视化 C# asp.net PhoneGap html5 C# Where 网站分布式开发简介 EntityFramework Core依赖注入上下文方式不同造成内存泄漏了解一下? SQL Server之深入理解STUFF 你必须知道的EntityFramework 6.x和EntityFramework Cor

    Webservice WCF WebApi   注明:改编加组合 在.net平台下,有大量的技术让你创建一个HTTP服务,像Web Service,WCF,现在又出了Web API.在.net平台下, ...

  3. net core 依赖注入问题

    net core 依赖注入问题 最近.net core可以跨平台了,这是一个伟大的事情,为了可以赶上两年以后的跨平台部署大潮,我也加入到了学习之列.今天研究的是依赖注入,但是我发现一个问题,困扰我很久 ...

  4. 实现BUG自动检测 - ASP.NET Core依赖注入

    我个人比较懒,能自动做的事绝不手动做,最近在用ASP.NET Core写一个项目,过程中会积累一些方便的工具类或框架,分享出来欢迎大家点评. 如果以后有时间的话,我打算写一个系列的[实现BUG自动检测 ...

  5. [译]ASP.NET Core依赖注入深入讨论

    原文链接:ASP.NET Core Dependency Injection Deep Dive - Joonas W's blog 这篇文章我们来深入探讨ASP.NET Core.MVC Core中 ...

  6. asp.net core 依赖注入几种常见情况

    先读一篇注入入门 全面理解 ASP.NET Core 依赖注入, 学习一下基本使用 然后学习一招, 不使用接口规范, 直接写功能类, 一般情况下可以用来做单例. 参考https://www.cnblo ...

  7. ASP.NET Core依赖注入——依赖注入最佳实践

    在这篇文章中,我们将深入研究.NET Core和ASP.NET Core MVC中的依赖注入,将介绍几乎所有可能的选项,依赖注入是ASP.Net Core的核心,我将分享在ASP.Net Core应用 ...

  8. 自动化CodeReview - ASP.NET Core依赖注入

    自动化CodeReview系列目录 自动化CodeReview - ASP.NET Core依赖注入 自动化CodeReview - ASP.NET Core请求参数验证 我个人比较懒,能自动做的事绝 ...

  9. ASP.NET Core 依赖注入基本用法

    ASP.NET Core 依赖注入 ASP.NET Core从框架层对依赖注入提供支持.也就是说,如果你不了解依赖注入,将很难适应 ASP.NET Core的开发模式.本文将介绍依赖注入的基本概念,并 ...

随机推荐

  1. python生成器 递归

    生成器 生成器:只要函数体内出现yield关键字,那么再执行函数就不会执行函数代码,会得到一个结果,该结果就是生成器   生成器就是迭代器   yield的功能 1.yield为我们提供了一种自定义迭 ...

  2. 自监督SOTA框架 | BYOL(优雅而简洁) | 2020

    文章原创自微信公众号「机器学习炼丹术」 作者:炼丹兄 联系方式:微信cyx645016617 本篇文章主要讲解两个无监督2020年比较新比较火的论文: 论文名称:"Bootstrap You ...

  3. 用git合并分支时,如何保持某些文件不被合并

    用git合并分支时,如何保持某些文件不被合并_fkaking的专栏-CSDN博客_git 合并分支 https://blog.csdn.net/fkaking/article/details/4495 ...

  4. 反射型XSS

    反射型XSS漏洞详解 http://www.ttlsa.com/safe/xss-description/ 一.原理 如果一个应用程序使用动态页面向用户显示错误消息,就会造成一种常见的XSS漏洞.通常 ...

  5. Spark调优 | Spark Streaming 调优

    Spark调优 | Spark Streaming 调优 1.数据序列化 2.广播大变量 3.数据处理和接收时的并行度 4.设置合理的批处理间隔 5.内存优化 5.1 内存管理 5.2优化策略 5.3 ...

  6. VMware vCenter 6.0 安装及群集配置介绍(转载)

    转载自http://blog.51cto.com/wzlinux/2094598 一.介绍 VMware vCenter Server 提供了一个可伸缩.可扩展的平台,为虚拟化管理奠定了基础.可集中管 ...

  7. Be accepted for inclusion in the IEEE INFOCOM 2018 technical program

    中了一篇INFOCOM,虽然不是一作但也是入学之后一直做的一份工作,算是没白下功夫吧.超声波定位这类工作,老实说,想应用到实际产品中,还是有一段路要走的. 老实说我也一直犹豫毕设的这套东西搞清楚了要不 ...

  8. Spring Boot 之遇见JSON

    MVC框架中,Spring Boot内置了jackson来完成JSON的序列化和反序列化操作,并且,在与其他技术集成的时候,如Redis.MongoDB.Elasticsearch等对象序列化,都可使 ...

  9. linux驴杂记

    1. 命令行 vim下 的Ctrl + S ,ctrl +Q: 这个功能可以用于长时间编译的时候随时把屏幕输出停住,但是编译仍然会继续,这样就不会影响到编译时间了. S:是锁屏  q:是开屏 2. S ...

  10. java-poi创建模板

    package com.jy.demo.web; import java.io.FileOutputStream; import org.apache.poi.ss.usermodel.Cell; i ...