博客列表 >探索 .NET Core的依赖注入

探索 .NET Core的依赖注入

Smile
Smile原创
2021年12月10日 13:27:52490浏览

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

  

  

  Exe

  net5.0

  

  

  

  

  

  现在我们开始注册我们的服务,但是我们需要一个IServiceCollection,让我们看一下IServiceCollection的定义。

  public interface IServiceCollection : IList

  {

  }

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

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

  public class ServiceCollection : IServiceCollection

  {

  private readonly List _descriptors=new List();

  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,然后注册两个QQ靓号买卖平台服务。

  static void Main(string[] args)

  {

  var serviceCollection=new ServiceCollection();

  serviceCollection.AddSingleton();

  serviceCollection.AddSingleton();

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

  serviceCollection.AddSingleton();

  这是最终的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类很简单,代表一个注册的服务,包括其服务类型,实现类型和生命周期。

  实例注册

  我们也可以手动new一个实例,然后传入到AddSingleton()方法中:

  var myInstance=new ClassB();

  serviceCollection.AddSingleton(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集合中。

声明:本文内容转载自脚本之家,由网友自发贡献,版权归原作者所有,如您发现涉嫌抄袭侵权,请联系admin@php.cn 核实处理。
全部评论
文明上网理性发言,请遵守新闻评论服务协议