0
点赞
收藏
分享

微信扫一扫

.NET Core 依赖注入的 IServiceCollection


.NET Core 依赖注入的 IServiceCollection

  • ​​什么是依赖注入(DI)和DI容器?​​
  • ​​注册服务​​
  • ​​服务生命周期​​
  • ​​我们注册服务时会发生什么?​​
  • ​​实例注册​​
  • .NET Core内置依赖项注入容器 ​​Microsoft.Extensions.DependencyInjection​

什么是依赖注入(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()方法

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

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()
{
// ...
}
}

  加一个接口IThing,现在,我们已经应用了SOLID的依赖倒置原则,我们不再依赖具体的实现,相反,我们依赖于IThing抽象,在构造函数中,只需要传入IThing的实现就好了

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()
{
// ...
}
}

手动new了一个ClassB,它实现了IThing接口,然后创建ClassA的时候,直接把thing传入构造函数中

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

  • 在实际中呢,我们代码中有很多类型,然后有各种各样的依赖关系
  • 这个时候我们就需要一个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​​集合中


举报

相关推荐

0 条评论