探索 .NET Core 依赖注入的 IServiceCollection
如果您使用了.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的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 日志框架NLog之将日志发送到邮件
- 下一篇: EFCore查缺补漏(二):查询