了解 .NET 中的相依性插入基本概念
在本文中,您會建立 .NET 主控台應用程式,以手動建立 ServiceCollection 和對應的 ServiceProvider。 您將了解如何註冊服務,並使用相依性插入 (DI) 來解析它們。 本文使用 Microsoft.Extensions.DependencyInjection NuGet 套件來示範 .NET 中 DI 的基本概念。
注意
本文不會利用泛型主機功能。 如需更完整的指南,請參閱在 .NET 中使用相依性插入。
開始使用
若要開始使用,請建立名為 DI.Basics 的新 .NET 主控台應用程式。 下列清單中會參考建立主控台專案的一些最常見方法:
- Visual Studio:[檔案] > [新增] > [專案] 功能表。
- Visual Studio Code 和 C# 開發套件延伸模組的:[方案總管] 功能表選項。
- .NET CLI:
dotnet new console
終端中的 命令。
您必須將套件參考新增至專案檔中的 Microsoft.Extensions.DependencyInjection。 無論使用何種方法,請確定專案類似於 DI.Basics.csproj 檔案的下列 XML:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
</ItemGroup>
</Project>
相依性插入基本概念
相依性插入是一種設計模式,可讓您移除硬式編碼的相依性,並讓您的應用程式更容易進行維護及測試。 DI 是一種在類別與其相依性之間實現控制反轉 (IoC) 的技術。
.NET 中 DI 的抽象概念定義於 Microsoft.Extensions.DependencyInjection.Abstractions NuGet 套件中:
- IServiceCollection:定義服務描述項集合的合約。
- IServiceProvider:定義擷取服務物件的機制。
- ServiceDescriptor:描述具有服務類型、實作和存留期的服務。
在 .NET 中,DI 會藉由新增服務並在 IServiceCollection
中設定它們來管理。 註冊服務之後,呼叫 BuildServiceProvider 方法會建置 IServiceProvider
執行個體。 IServiceProvider
作為所有已註冊服務的容器,並用來解析服務。
建立 example 服務
並非所有服務都同樣會建立。 某些服務每次在服務容器取得它們時都需要新的執行個體 (transient),而其他服務則應該跨要求 (scoped) 或針對整個應用程式 (singleton) 存留期共用。 如需服務存留期的詳細資訊,請參閱服務存留期。
同樣地,某些服務只會公開具體類型,而其他服務則以介面與實作類型之間的合約表示。 您可以建立數種服務的變化,以協助示範這些概念。
建立名為 IConsole.cs 的新 C# 檔案,並新增下列程式碼:
public interface IConsole
{
void WriteLine(string message);
}
此檔案會定義公開單一方法 WriteLine
的 IConsole
介面。 接下來,建立名為 DefaultConsole.cs 的新 C# 檔案,並新增下列程式碼:
internal sealed class DefaultConsole : IConsole
{
public bool IsEnabled { get; set; } = true;
void IConsole.WriteLine(string message)
{
if (IsEnabled is false)
{
return;
}
Console.WriteLine(message);
}
}
上述程式碼代表 IConsole
介面的預設實作。 WriteLine
方法會根據 IsEnabled
屬性有條件地寫入主控台。
提示
實作的命名是您開發小組應該同意的選擇。 Default
前置詞是指出介面「預設」實作的常見慣例,但「並非」必要。
接下來,建立 IGreetingService.cs 檔案,並新增下列 C# 程式碼:
public interface IGreetingService
{
string Greet(string name);
}
然後新增名為 DefaultGreetingService.cs 的新 C# 檔案,並新增下列程式碼:
internal sealed class DefaultGreetingService(
IConsole console) : IGreetingService
{
public string Greet(string name)
{
var greeting = $"Hello, {name}!";
console.WriteLine(greeting);
return greeting;
}
}
上述程式碼代表 IGreetingService
介面的預設實作。 服務實作需要 IConsole
作為主要建構函式參數。 Greet
方法:
- 建立
greeting
並指定name
。 - 在
IConsole
執行個體上呼叫WriteLine
方法。 - 將
greeting
傳回給呼叫端。
要建立的最後一個服務是 FarewellService.cs 檔案,請在繼續之前新增下列 C# 程式碼:
public class FarewellService(IConsole console)
{
public string SayGoodbye(string name)
{
var farewell = $"Goodbye, {name}!";
console.WriteLine(farewell);
return farewell;
}
}
FarewellService
表示具體類型,而非介面。 它應該宣告為 public
,使其可供取用者存取。 不同於宣告為 internal
和 sealed
的其他服務實作類型,此程式碼會示範並非所有服務都必須是介面。 它也會顯示服務實作可以是 sealed
以防止繼承,而 internal
會限制對元件的存取。
更新 Program
類別
開啟 Program.cs 檔案,並以下列 C# 程式碼取代現有的程式碼:
using Microsoft.Extensions.DependencyInjection;
// 1. Create the service collection.
var services = new ServiceCollection();
// 2. Register (add and configure) the services.
services.AddSingleton<IConsole>(
implementationFactory: static _ => new DefaultConsole
{
IsEnabled = true
});
services.AddSingleton<IGreetingService, DefaultGreetingService>();
services.AddSingleton<FarewellService>();
// 3. Build the service provider from the service collection.
var serviceProvider = services.BuildServiceProvider();
// 4. Resolve the services that you need.
var greetingService = serviceProvider.GetRequiredService<IGreetingService>();
var farewellService = serviceProvider.GetRequiredService<FarewellService>();
// 5. Use the services
var greeting = greetingService.Greet("David");
var farewell = farewellService.SayGoodbye("David");
上述更新的程式碼會示範如何:
- 建立新的
ServiceCollection
執行個體。 - 在
ServiceCollection
中註冊及設定服務:IConsole
使用實作處理站多載、傳回DefaultConsole
類型,並將IsEnabled
設定為 true。IGreetingService
會新增且具有DefaultGreetingService
類型的對應實作類型。FarewellService
會新增為具體類型。
- 從
ServiceCollection
建置ServiceProvider
。 - 解析
IGreetingService
和FarewellService
服務。 - 使用已解析的服務來向名為
David
的人員問候並告別。
如果您將 DefaultConsole
的 IsEnabled
屬性更新為 false
,則 Greet
和 SayGoodbye
方法會省略將所產生訊息寫入至主控台的操作。 此類變更有助於示範 IConsole
服務已插入 IGreetingService
和 FarewellService
服務,作為影響應用程式行為的相依性。
所有這些服務都會註冊為單一資料庫,但在此範例中,如果這些服務註冊 transient 或 scoped 服務,則其運作方式相同。
重要
在此人為設計的 example 中,服務存留期並不重要,但在真實世界的應用程式中,您應該仔細考慮每個服務的存留期。
執行範例應用程式
若要執行範例應用程式,請在 Visual Studio、Visual Studio Code 中按 F5,或在終端中執行 dotnet run
命令。 當應用程式完成時,您應該會看到下列輸出:
Hello, David!
Goodbye, David!
裝置描述項
將服務新增至 ServiceCollection
時所使用最常見的 API 是存留期命名的泛型擴充方法,例如:
AddSingleton<TService>
AddTransient<TService>
AddScoped<TService>
這些方法是建立 ServiceDescriptor 執行個體並將其新增至 ServiceCollection
的便利方法。 ServiceDescriptor
是一個簡單的類別,描述服務所具有的服務類型、實作類型和存留期。 它也可以描述實作處理站和執行個體。
針對您在 ServiceCollection
中註冊的每個服務,您可以改為直接使用 ServiceDescriptor
執行個體來呼叫 Add
方法。 請參考下列範例:
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(IConsole),
implementationFactory: static _ => new DefaultConsole
{
IsEnabled = true
},
lifetime: ServiceLifetime.Singleton));
上述程式碼相當於 IConsole
服務在 ServiceCollection
中註冊的方式。 Add
方法可用來新增描述 IConsole
服務的 ServiceDescriptor
執行個體。 靜態方法 ServiceDescriptor.Describe
會委派給各種 ServiceDescriptor
建構函式。 請考慮 IGreetingService
服務的對等程式碼:
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(IGreetingService),
implementationType: typeof(DefaultGreetingService),
lifetime: ServiceLifetime.Singleton));
上述程式碼描述 IGreetingService
服務所具有的服務類型、實作類型和存留期。 最後,請考慮 FarewellService
服務的對等程式碼:
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(FarewellService),
implementationType: typeof(FarewellService),
lifetime: ServiceLifetime.Singleton));
上述程式碼會將具體 FarewellService
類型描述為服務和實作類型。 服務會註冊為 singleton 服務。