在本文中,您將建立 .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="10.0.0" />
</ItemGroup>
</Project>
依賴注入基礎知識
相依性插入是一種設計模式,可讓您移除硬式編碼的相依性,並讓您的應用程式更容易進行維護及測試。 DI 是一種在類別與其相依性之間實現控制反轉 (IoC) 的技術。
.NET 中 DI 的抽象概念定義於 Microsoft.Extensions.DependencyInjection.Abstractions NuGet 套件中:
- IServiceCollection:定義服務描述項集合的合約。
- IServiceProvider:定義擷取服務物件的機制。
- ServiceDescriptor:描述具有服務類型、實作和存留期的服務。
在 .NET 中,DI 會藉由新增服務並在 IServiceCollection 中設定它們來管理。 註冊服務之後,透過呼叫 IServiceProvider 方法來建置 BuildServiceProvider 執行個體。
IServiceProvider 作為所有已註冊服務的容器,並用來解析服務。
建立範例服務
並非所有服務都是相同的。 某些服務每次服務容器取得它們時都需要新的實例(瞬態),而其他服務應在請求之間共用(範圍限定),或在整個應用程式的生命週期中共用(單例)。 如需服務存留期的詳細資訊,請參閱服務存留期。
同樣地,某些服務只會公開具體類型,而其他服務則以介面與實作類型之間的合約表示。 您建立幾個服務的變體,以幫助示範這些概念。
建立名為 IConsole.cs 的新 C# 檔案,並新增下列程式碼:
public interface IConsole
{
void WriteLine(string message);
}
此檔案會定義公開單一方法 IConsole 的 WriteLine 介面。 接下來,建立名為 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。 - 在
WriteLine實例上呼叫IConsole方法。 - 將
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會新增為具體的類型。
- 使用實作工廠重載的
- 從
ServiceProvider建置ServiceCollection。 - 解析
IGreetingService和FarewellService服務。 - 使用已解析的服務來向名為
David的人員問候並告別。
如果您將 IsEnabled 的 DefaultConsole 屬性更新為 false,則 Greet 和 SayGoodbye 方法將不會將生成的訊息寫入主控台。 此類變更有助於說明 IConsole 服務已被插入到 和 IGreetingService 服務中,作為影響應用程式行為的相依性FarewellService。
所有這些服務都會註冊為 Singleton 單例服務,不過在此範例中,即使註冊為 Transient
重要
在此精心嘗試的範例中,服務存留期並不重要,但在真實世界中,您應該仔細考慮每個服務的存留期。
執行範例應用程式
若要執行範例應用程式,請在 Visual Studio、Visual Studio Code 中按 F5,或在終端中執行 dotnet run 命令。 當應用程式完成時,您應該會看到下列輸出:
Hello, David!
Goodbye, David!
服務描述符
將服務新增至 ServiceCollection 時所使用最常見的 API 是存留期命名的泛型擴充方法,例如:
AddSingleton<TService>AddTransient<TService>AddScoped<TService>
這些方法是建立 ServiceDescriptor 執行個體並將其新增至 ServiceCollection 的便利方法。
ServiceDescriptor 是一個簡單的類別,描述服務所具有的服務類型、實作類型和存留期。 它也可以描述實作處理站和實例。
針對您在 ServiceCollection 中註冊的每個服務,您可以改為直接使用 Add 執行個體來呼叫 ServiceDescriptor 方法。 請參考下列範例:
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(IConsole),
implementationFactory: static _ => new DefaultConsole
{
IsEnabled = true
},
lifetime: ServiceLifetime.Singleton));
上述程式碼相當於 IConsole 服務在 ServiceCollection 中註冊的方式。
Add 方法用來新增一個描述 ServiceDescriptor 服務的 IConsole 執行個體。 靜態方法 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 類型描述為服務和實作類型。 服務會註冊為單一服務。