如果您使用了.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