了解 .NET 中的相依性插入基本概念

在本文中,您會建立 .NET 主控台應用程式,以手動建立 ServiceCollection 和對應的 ServiceProvider。 您將了解如何註冊服務,並使用相依性插入 (DI) 來解析它們。 本文使用 Microsoft.Extensions.DependencyInjection NuGet 套件來示範 .NET 中 DI 的基本概念。

注意

本文不會利用泛型主機功能。 如需更完整的指南,請參閱在 .NET 中使用相依性插入

開始使用

若要開始使用,請建立名為 DI.Basics 的新 .NET 主控台應用程式。 下列清單中會參考建立主控台專案的一些最常見方法:

您必須將套件參考新增至專案檔中的 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 套件中:

在 .NET 中,DI 會藉由新增服務並在 IServiceCollection 中設定它們來管理。 註冊服務之後,呼叫 BuildServiceProvider 方法會建置 IServiceProvider 執行個體。 IServiceProvider 作為所有已註冊服務的容器,並用來解析服務。

建立範例服務

並非所有服務都同樣會建立。 某些服務每次在服務容器取得它們時都需要新的執行個體 (暫時性),而其他服務則應該跨要求 (有限範圍) 或針對整個應用程式 (單一資料庫) 存留期共用。 如需服務存留期的詳細資訊,請參閱服務存留期

同樣地,某些服務只會公開具體類型,而其他服務則以介面與實作類型之間的合約表示。 您可以建立數種服務的變化,以協助示範這些概念。

建立名為 IConsole.cs 的新 C# 檔案,並新增下列程式碼:

public interface IConsole
{
    void WriteLine(string message);
}

此檔案會定義公開單一方法 WriteLineIConsole 介面。 接下來,建立名為 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。
    • IGreetingService 會新增且具有 DefaultGreetingService 類型的對應實作類型。
    • FarewellService 會新增為具體類型。
  • ServiceCollection 建置 ServiceProvider
  • 解析 IGreetingServiceFarewellService 服務。
  • 使用已解析的服務來向名為 David 的人員問候並告別。

如果您將 DefaultConsoleIsEnabled 屬性更新為 false,則 GreetSayGoodbye 方法會省略將所產生訊息寫入至主控台的操作。 此類變更有助於示範 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 方法可用來新增描述 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 類型描述為服務和實作類型。 服務會註冊為單一資料庫服務。

另請參閱