共用方式為


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

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

備註

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

開始

要開始,請建立一個新的 .NET 主控台應用程式,名為 DI.Basics。 在 Visual Studio 中,選擇 「檔案 > 新 > 專案」,或使用 .NET CLI 輸入 dotnet new console

接著,在專案檔案中新增 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.5" />
  </ItemGroup>

</Project>

依賴注入基礎知識

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

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

在 .NET 中,你透過新增服務並在 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
  • WriteLine實例上呼叫IConsole方法。
  • 回傳 greeting 給來電者。

這個 DefaultGreetingService 類別展示了你可以 seal 服務實作,以防止繼承,並使用 internal 來限制對程序集的存取。

最後一個要建立的服務是 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的其他服務實作類型不同,這段程式碼顯示並非所有服務都需要是介面。

更新類別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 並設置其屬性 IsEnabledtrue
    • 服務的 IGreetingService 實作類型為 DefaultGreetingService
    • FarewellService服務作為具體類型。
  • ServiceProvider建構ServiceCollection
  • 解決〈c0〉服務與〈c1〉服務。
  • 利用已解決的服務向一位名叫 David的人問候並道別。

如果你將IsEnabledDefaultConsole屬性更新為falseGreetSayGoodbye方法就不會將生成的消息寫入主控台。 此變更有助於證明IConsole服務被注入到IGreetingServiceFarewellService服務中,作為一種依賴項,影響應用程式的行為。

所有這些服務都註冊為單例。 在這個範例中,將它們註冊為 暫態有範圍 的服務,運作方式相同。

這很重要

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

執行範例應用程式

要執行範例應用程式,請在 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 方法會新增一個 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 型別描述為服務型與實作型態。 該服務註冊為單例服務。

另請參閱