在本快速入门中,你将创建一个 .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 的抽象:
- IServiceCollection:定义服务描述符集合的协定。
- IServiceProvider:定义用于检索服务对象的机制。
- ServiceDescriptor:描述具有服务类型、实现和生存期的服务。
在 .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);
}
}
前面的代码表示接口的默认实现 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 使其可供使用者访问。 与被声明为 internal 和 sealed 的其他服务实现类型不同,此代码表明,并非所有服务都需要是接口。
更新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的人告别。
如果将DefaultConsole的IsEnabled属性更新为false,则Greet和SayGoodbye方法将省略把生成的消息写入控制台。 此更改有助于展示IConsole服务被作为依赖项注入到IGreetingService和FarewellService服务中,以此影响应用的行为。
所有这些服务都注册为单一实例。 对于此示例,如果将它们注册为 暂时 服务或 范围 服务,则其工作方式相同。
重要
在这个人为的示例中,服务生命周期并不重要。 在实际应用程序中,仔细考虑每个服务的生存期。
运行示例应用
若要运行示例应用,请在 Visual Studio 或 Visual Studio Code 中按 F5 ,或在终端中运行 dotnet run 命令。 应用完成后,会看到以下输出:
Hello, David!
Goodbye, David!
服务描述符
最常用的 API 用于将服务添加到ServiceCollection的是一些具有生命周期命名的泛型扩展方法,例如:
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));
上述代码等效于服务在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 类型描述为服务和实现类型。 该服务注册为单例服务。