通过


快速入门:.NET 中的依赖项注入基础知识

在本快速入门中,你将创建一个 .NET 控制台应用,并手动创建 ServiceCollection 和相应的 ServiceProvider。 了解如何使用依赖项注入注册服务并解决它们。 本文使用 Microsoft.Extensions.DependencyInjection NuGet 包来演示 .NET 中 DI 的基础知识。

注释

本文不利用 通用主机 功能。 有关更全面的指南,请参阅 在 .NET 中使用依赖项注入

开始

若要开始,请创建新的名为 DI.Basics 的 .NET 控制台应用程序。 在 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 中添加服务并配置它们来管理 DI。 注册服务后,调用 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);
    }
}

前面的代码表示接口的默认实现 IConsoleWriteLine 方法根据 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 方法:

  • 创建给定 greetingname.
  • 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类型,其中属性IsEnabled设置为true
    • DefaultGreetingService服务具有相应的IGreetingService实现类型。
    • 服务 FarewellService 作为具体类型。
  • ServiceProvider构建ServiceCollection
  • 解析IGreetingService服务和FarewellService服务。
  • 使用解析的服务来问候并向名叫David的人告别。

如果将DefaultConsoleIsEnabled属性更新为false,则GreetSayGoodbye方法将省略把生成的消息写入控制台。 此更改有助于展示IConsole服务被作为依赖项注入到IGreetingServiceFarewellService服务中,以此影响应用的行为。

所有这些服务都注册为单一实例。 对于此示例,如果将它们注册为 暂时 服务或 范围 服务,则其工作方式相同。

重要

在这个人为的示例中,服务生命周期并不重要。 在实际应用程序中,仔细考虑每个服务的生存期。

运行示例应用

若要运行示例应用,请在 Visual Studio 或 Visual Studio Code 中按 F5 ,或在终端中运行 dotnet run 命令。 应用完成后,会看到以下输出:

Hello, David!
Goodbye, David!

服务描述符

最常用的 API 用于将服务添加到ServiceCollection的是一些具有生命周期命名的泛型扩展方法,例如:

  • AddSingleton<TService>
  • AddTransient<TService>
  • AddScoped<TService>

这些方法是便捷方法,用于创建ServiceDescriptor实例并将其添加到ServiceCollectionServiceDescriptor这是一个简单的类,描述具有服务类型、实现类型和生存期的服务。 它还可以描述实现工厂和实例。

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 类型描述为服务和实现类型。 该服务注册为单例服务。

另请参阅