共用方式為


快速入門:.NET 中的相依注入基礎

在這個快速入門中,您將建立一個 .NET 控制台應用程式,並手動建立 ServiceCollection 及其對應的 ServiceProvider。 你會學會如何註冊服務並利用依賴注入(DI)來解決服務。 本文使用 Microsoft.Extensions.DependencyInjection NuGet 套件來示範 .NET 中 DI 的基本操作。

備註

本文未利用 Generic 主機 的功能。 欲獲得更完整的指南,請參閱 在 .NET 中使用相依性注入

開始

要開始,請建立一個新的 .NET 主控台應用程式,名為 DI.Basics。 以下列出了一些最常見的主控台專案創建方法:

你需要在專案檔案中新增 Microsoft.Extensions.DependencyInjection 的套件參考。 無論採用哪種方法,請確保專案與以下 DI.Basics.csproj 檔案的 XML 相似:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.2" />
  </ItemGroup>

</Project>

依賴注入基礎知識

相依注入是一種設計模式,讓你能移除硬編碼的相依關係,讓你的應用程式更易維護且可測試。 DI 是一種用於在類別及其相依關係間實現 控制反轉(IoC )的技術。

.NET 中 DI 的抽象定義在 Microsoft.Extensions.DependencyInjection.Abstractions NuGet 套件中:

在 .NET 中,DI 是透過在 IServiceCollection 中新增服務並配置它們來管理的。 服務註冊後,透過呼叫 BuildServiceProvider 方法建立 IServiceProvider 實例。 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
  • 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 聲明,以便讓消費者可以存取。 與其他宣告為 internalsealed的服務實作類型不同,這段程式碼顯示並非所有服務都需要是介面。 同時也顯示服務實作可以用 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
    • DefaultGreetingService類型的對應實作類型加入IGreetingService
    • FarewellService 被添加為具體類型。
  • ServiceCollection建構ServiceProvider
  • 解決〈c0〉服務與〈c1〉服務。
  • 利用已解決的服務向一位名叫 David的人問候並道別。

如果你將DefaultConsoleIsEnabled屬性更新為false,則GreetSayGoodbye方法會省略將結果訊息寫入控制台。 這樣的變更有助於證明該IConsole服務作為一個相依性被注入到IGreetingServiceFarewellService服務中,從而影響應用程式的行為。

所有這些服務都註冊為 singleton,雖然在這個範例中,即使它們註冊為 transientscoped 服務,運作效果也是相同的。

這很重要

在這個刻意的例子中,服務壽命並不重要,但在實際應用中,你應該仔細考慮每個服務的使用壽命。

執行範例應用程式

要執行範例應用程式,請在 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));

前述代碼的作用等同於在 ServiceCollection 中註冊 IConsole 服務的方式。 此 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 型別描述為服務型與實作型態。 該服務註冊為單例服務。

另請參閱