若是您使用了.NET Core,則極可能已使用Microsoft.Extensions.DependencyInjection
中的內置依賴項注入容器,在本文中,我想更深刻地瞭解Microsoft Dependency Injection(DI)
容器中的 IServiceCollection。安全
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,而後直接添加到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