共用方式為


了解 .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 作為所有已註冊服務的容器,並用來解析服務。

建立 example 服務

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

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

建立名為 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 服務,作為影響應用程式行為的相依性

所有這些服務都會註冊為單一資料庫,但在此範例中,如果這些服務註冊 transientscoped 服務,則其運作方式相同。

重要

在此人為設計的 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 服務。

另請參閱