一、服务注册的核心概念
1. 什么是服务注册?
服务注册是.NET 依赖注入(DI)体系中的核心操作,本质是将服务类型(接口 / 抽象类)与具体实现类型、生命周期绑定,并存储到 DI 容器中 的过程。
- 服务类型 :对外暴露的抽象契约(通常是接口 / 抽象类),比如
IUserService。 - 实现类型 :服务类型的具体实现类,比如
UserService。 - DI 容器:存储服务注册信息的 "容器",程序运行时可从中 "取出"(解析)服务实例。
- 生命周期:决定 DI 容器创建服务实例的规则,是服务注册的核心属性。
2. 服务的三种核心生命周期
|-------------------|-----------------------------------------------------------------------|-----------------------------|
| 生命周期类型 | 核心特点 | | 适用场景 | |------| |
| 瞬时(Transient) | 每次解析都创建全新实例 | 无状态、轻量级服务(如工具类、数据验证器) |
| 作用域(Scoped) | 同一个作用域内解析多次,返回同一个实例;不同作用域返回不同实例 | 有状态的短期服务(如数据库上下文 DbContext) |
| 单例(Singleton) | |--------------------------------| | 整个应用生命周期内,无论解析多少次,都返回同一个实例 | | 无状态、全局复用的服务(如配置管理器、缓存服务) |
3. 服务注册的核心价值
- 解耦:调用方只依赖抽象(接口),不依赖具体实现,符合 "依赖倒置原则";
- 可替换:修改实现类时,只需修改注册逻辑,无需改动调用代码;
- 自动管理生命周期 :DI 容器自动创建、销毁实例,无需手动
new和释放; - 简化测试:可轻松替换为模拟(Mock)服务,方便单元测试。
二、服务注册的核心用法
.NET 提供了IServiceCollection接口作为服务注册的入口,核心注册方法如下:
|---------------------------------------------|-----------|------------------------------------------------------------|
| 注册方法 | 用途 | 示例 |
| AddTransient<TService, TImplementation>() | 注册瞬时服务 | services.AddTransient<IUserService, UserService>() |
| AddScoped<TService, TImplementation>() | 注册作用域服务 | services.AddScoped<IOrderService, OrderService>() |
| AddSingleton<TService, TImplementation>() | 注册单例服务 | services.AddSingleton<ICacheService, CacheService>() |
| AddSingleton<TService>(instance) | 注册已实例化的单例 | services.AddSingleton<ICacheService>(new CacheService()) |
| Add<TService>() | 简写(默认瞬时) | services.Add<UserService>() |
三、控制台案例
步骤 1:创建控制台项目(环境准备)
-
打开 Visual Studio/VS Code,创建.NET 8(或更高版本)控制台项目;
-
确保项目文件(
.csproj)包含Microsoft.Extensions.DependencyInjection包(默认已包含,若缺失可通过 NuGet 安装):cs<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>步骤 2:定义服务接口和实现类
先定义 3 个不同生命周期的服务:
cs
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
// ====================== 1. 定义服务接口(抽象契约) ======================
/// <summary>
/// 瞬时服务接口:用户服务
/// </summary>
public interface IUserService
{
// 获取服务实例ID(用于验证生命周期)
Guid GetServiceInstanceId();
// 模拟业务方法:获取用户名
string GetUserName(int userId);
}
/// <summary>
/// 作用域服务接口:订单服务
/// </summary>
public interface IOrderService
{
Guid GetServiceInstanceId();
// 模拟业务方法:获取订单号
string GetOrderNo(int orderId);
}
/// <summary>
/// 单例服务接口:缓存服务
/// </summary>
public interface ICacheService
{
Guid GetServiceInstanceId();
// 模拟业务方法:设置缓存
void SetCache(string key, string value);
// 模拟业务方法:获取缓存
string? GetCache(string key);
}
// ====================== 2. 实现服务类 ======================
/// <summary>
/// 用户服务实现(瞬时)
/// </summary>
public class UserService : IUserService
{
// 每个实例唯一ID,用于验证生命周期
private readonly Guid _instanceId;
// 构造函数:创建实例时生成唯一ID
public UserService()
{
_instanceId = Guid.NewGuid();
Console.WriteLine($"【UserService】实例已创建,ID:{_instanceId}");
}
public Guid GetServiceInstanceId() => _instanceId;
public string GetUserName(int userId)
{
return userId switch
{
1 => "张三",
2 => "李四",
_ => "未知用户"
};
}
}
/// <summary>
/// 订单服务实现(作用域)
/// </summary>
public class OrderService : IOrderService
{
private readonly Guid _instanceId;
public OrderService()
{
_instanceId = Guid.NewGuid();
Console.WriteLine($"【OrderService】实例已创建,ID:{_instanceId}");
}
public Guid GetServiceInstanceId() => _instanceId;
public string GetOrderNo(int orderId)
{
return $"ORDER_{orderId}_{DateTime.Now:yyyyMMdd}";
}
}
/// <summary>
/// 缓存服务实现(单例)
/// </summary>
public class CacheService : ICacheService
{
private readonly Guid _instanceId;
// 模拟缓存存储
private readonly Dictionary<string, string> _cacheDict = new();
public CacheService()
{
_instanceId = Guid.NewGuid();
Console.WriteLine($"【CacheService】实例已创建,ID:{_instanceId}");
}
public Guid GetServiceInstanceId() => _instanceId;
public void SetCache(string key, string value)
{
_cacheDict[key] = value;
Console.WriteLine($"【CacheService】缓存已设置:{key} = {value}");
}
public string? GetCache(string key)
{
_cacheDict.TryGetValue(key, out var value);
return value;
}
}
步骤 3:服务注册与解析
编写主程序,完成服务注册、不同生命周期的解析验证:
cs
/// <summary>
/// 主程序
/// </summary>
class Program
{
static void Main(string[] args)
{
Console.WriteLine("===== .NET服务注册与依赖注入演示 =====");
Console.WriteLine();
// ====================== 步骤1:创建服务集合(ServiceCollection),用于注册服务 ======================
// IServiceCollection是服务注册的容器,本质是存储服务描述的列表
var services = new ServiceCollection();
// ====================== 步骤2:注册服务(核心操作) ======================
Console.WriteLine("【第一步】开始注册服务...");
// 1. 注册瞬时服务:每次解析都创建新实例
services.AddTransient<IUserService, UserService>();
// 2. 注册作用域服务:同一个作用域内实例唯一
services.AddScoped<IOrderService, OrderService>();
// 3. 注册单例服务:整个应用生命周期内实例唯一
// 方式1:通过类型注册(推荐,容器自动创建实例)
services.AddSingleton<ICacheService, CacheService>();
// 方式2:直接注册已实例化的单例(适用于需要手动初始化的场景)
// var cacheInstance = new CacheService();
// services.AddSingleton<ICacheService>(cacheInstance);
Console.WriteLine("【第一步】服务注册完成!");
Console.WriteLine();
// ====================== 步骤3:构建服务提供器(ServiceProvider),用于解析服务 ======================
// ServiceProvider是DI容器的核心,负责根据注册信息创建/解析服务实例
using var serviceProvider = services.BuildServiceProvider();
// ====================== 步骤4:验证不同生命周期的服务特性 ======================
Console.WriteLine("【第二步】验证瞬时服务(Transient)特性:每次解析都是新实例");
VerifyTransientService(serviceProvider);
Console.WriteLine();
Console.WriteLine("【第三步】验证作用域服务(Scoped)特性:同作用域内实例唯一,不同作用域不同实例");
VerifyScopedService(serviceProvider);
Console.WriteLine();
Console.WriteLine("【第四步】验证单例服务(Singleton)特性:全局唯一实例");
VerifySingletonService(serviceProvider);
Console.WriteLine();
Console.WriteLine("【第五步】服务依赖注入演示(服务嵌套调用)");
VerifyServiceDependency(serviceProvider);
Console.WriteLine();
Console.WriteLine("===== 演示结束 =====");
Console.ReadLine();
}
#region 辅助方法:验证不同生命周期的服务
/// <summary>
/// 验证瞬时服务:每次解析都是新实例
/// </summary>
private static void VerifyTransientService(IServiceProvider serviceProvider)
{
// 第一次解析瞬时服务
var userService1 = serviceProvider.GetRequiredService<IUserService>();
Console.WriteLine($"第一次解析IUserService,实例ID:{userService1.GetServiceInstanceId()}");
Console.WriteLine($"业务方法调用:用户1的名称 = {userService1.GetUserName(1)}");
// 第二次解析瞬时服务(应该是新实例)
var userService2 = serviceProvider.GetRequiredService<IUserService>();
Console.WriteLine($"第二次解析IUserService,实例ID:{userService2.GetServiceInstanceId()}");
Console.WriteLine($"业务方法调用:用户2的名称 = {userService2.GetUserName(2)}");
// 验证:两次解析的实例ID是否不同(瞬时服务特性)
Console.WriteLine($"瞬时服务验证结果:两次实例是否相同? {userService1.GetServiceInstanceId() == userService2.GetServiceInstanceId()}");
}
/// <summary>
/// 验证作用域服务:同作用域内实例唯一,不同作用域不同实例
/// </summary>
private static void VerifyScopedService(IServiceProvider serviceProvider)
{
// 作用域1:创建第一个作用域
using (var scope1 = serviceProvider.CreateScope())
{
var scopeProvider1 = scope1.ServiceProvider;
// 作用域1内第一次解析
var orderService1_1 = scopeProvider1.GetRequiredService<IOrderService>();
Console.WriteLine($"作用域1 - 第一次解析IOrderService,实例ID:{orderService1_1.GetServiceInstanceId()}");
Console.WriteLine($"业务方法调用:订单1的编号 = {orderService1_1.GetOrderNo(1)}");
// 作用域1内第二次解析(应该是同一个实例)
var orderService1_2 = scopeProvider1.GetRequiredService<IOrderService>();
Console.WriteLine($"作用域1 - 第二次解析IOrderService,实例ID:{orderService1_2.GetServiceInstanceId()}");
Console.WriteLine($"作用域1内验证:两次实例是否相同? {orderService1_1.GetServiceInstanceId() == orderService1_2.GetServiceInstanceId()}");
}
// 作用域2:创建第二个作用域
using (var scope2 = serviceProvider.CreateScope())
{
var scopeProvider2 = scope2.ServiceProvider;
// 作用域2内解析(应该是新实例)
var orderService2 = scopeProvider2.GetRequiredService<IOrderService>();
Console.WriteLine($"作用域2 - 解析IOrderService,实例ID:{orderService2.GetServiceInstanceId()}");
Console.WriteLine($"业务方法调用:订单2的编号 = {orderService2.GetOrderNo(2)}");
}
}
/// <summary>
/// 验证单例服务:全局唯一实例
/// </summary>
private static void VerifySingletonService(IServiceProvider serviceProvider)
{
// 第一次解析单例服务
var cacheService1 = serviceProvider.GetRequiredService<ICacheService>();
cacheService1.SetCache("user_1", "张三");
Console.WriteLine($"第一次解析ICacheService,实例ID:{cacheService1.GetServiceInstanceId()}");
Console.WriteLine($"缓存读取:user_1 = {cacheService1.GetCache("user_1")}");
// 第二次解析单例服务(应该是同一个实例)
var cacheService2 = serviceProvider.GetRequiredService<ICacheService>();
Console.WriteLine($"第二次解析ICacheService,实例ID:{cacheService2.GetServiceInstanceId()}");
Console.WriteLine($"缓存读取(复用第一次设置的值):user_1 = {cacheService2.GetCache("user_1")}");
// 在作用域内解析单例(仍然是同一个实例)
using (var scope = serviceProvider.CreateScope())
{
var cacheService3 = scope.ServiceProvider.GetRequiredService<ICacheService>();
Console.WriteLine($"作用域内解析ICacheService,实例ID:{cacheService3.GetServiceInstanceId()}");
Console.WriteLine($"缓存读取:user_1 = {cacheService3.GetCache("user_1")}");
}
// 验证:所有解析的实例ID是否相同
var isSame = cacheService1.GetServiceInstanceId() == cacheService2.GetServiceInstanceId();
Console.WriteLine($"单例服务验证结果:所有实例是否相同? {isSame}");
}
/// <summary>
/// 验证服务依赖注入:一个服务依赖另一个服务
/// </summary>
private static void VerifyServiceDependency(IServiceProvider serviceProvider)
{
// 先注册一个依赖其他服务的新服务
// 定义一个业务服务,依赖IUserService和ICacheService
services.AddTransient<IBusinessService, BusinessService>(); // 注意:这里需要先把services变量改为类级别的静态变量,或重新注册
// 重新构建服务提供器(因为新增了注册)
using var newServiceProvider = services.BuildServiceProvider();
var businessService = newServiceProvider.GetRequiredService<IBusinessService>();
businessService.DoBusiness(1);
}
#endregion
}
// ====================== 补充:服务依赖的示例 ======================
/// <summary>
/// 业务服务接口(依赖其他服务)
/// </summary>
public interface IBusinessService
{
void DoBusiness(int userId);
}
/// <summary>
/// 业务服务实现(构造函数注入IUserService和ICacheService)
/// </summary>
public class BusinessService : IBusinessService
{
// 依赖的服务通过构造函数注入(DI容器自动解析)
private readonly IUserService _userService;
private readonly ICacheService _cacheService;
// 构造函数注入是.NET DI的默认方式(推荐)
public BusinessService(IUserService userService, ICacheService cacheService)
{
_userService = userService;
_cacheService = cacheService;
Console.WriteLine($"【BusinessService】实例已创建,依赖的IUserService实例ID:{userService.GetServiceInstanceId()}");
Console.WriteLine($"【BusinessService】实例已创建,依赖的ICacheService实例ID:{cacheService.GetServiceInstanceId()}");
}
public void DoBusiness(int userId)
{
var userName = _userService.GetUserName(userId);
_cacheService.SetCache($"business_user_{userId}", userName);
Console.WriteLine($"【BusinessService】业务处理完成:用户{userId}的名称是{userName},已缓存");
}
}
步骤 4:代码关键说明
-
服务注册核心:
ServiceCollection是 "注册清单",通过AddXxx方法将服务类型与实现类型、生命周期绑定;BuildServiceProvider()将注册清单转换为可解析服务的ServiceProvider(DI 容器)。
-
服务解析方法:
GetRequiredService<T>():强制解析服务,若服务未注册则抛出异常(推荐);GetService<T>():解析服务,若未注册则返回null;CreateScope():创建作用域,作用域内解析的 Scoped 服务实例唯一。
-
依赖注入方式:
- 示例中
BusinessService通过构造函数注入 依赖的IUserService和ICacheService,这是.NET DI 的默认且推荐的方式; - DI 容器会自动解析依赖链:解析
IBusinessService时,先解析其构造函数中的IUserService和ICacheService,再创建BusinessService实例。
- 示例中
步骤 5:运行结果分析
运行程序后,会看到以下关键输出:
- 瞬时服务 :两次解析的
IUserService实例 ID 完全不同; - 作用域服务 :同一个作用域内两次解析的
IOrderService实例 ID 相同,不同作用域 ID 不同; - 单例服务 :无论直接解析还是在作用域内解析,
ICacheService的实例 ID 始终相同,且缓存数据可复用; - 依赖注入 :
BusinessService创建时,DI 容器自动注入依赖的服务实例,无需手动new。
四、扩展知识点
- 服务注册的其他方式 :
- 泛型服务注册:
services.AddTransient(typeof(IGenericService<>), typeof(GenericService<>)); - 工厂模式注册:
services.AddTransient<IUserService>(sp => new UserService("自定义参数"));
- 泛型服务注册:
- 服务生命周期注意事项 :
- 禁止在 "短生命周期服务" 中注入 "长生命周期服务"(如 Scoped 服务注入 Singleton 服务),会导致 Scoped 服务被 "提升" 为 Singleton,引发线程安全问题;
- 瞬时服务可注入任意生命周期的服务,单例服务只能注入单例服务。
- 控制台项目 vs Web 项目 :
- Web 项目中,每个 HTTP 请求对应一个作用域(Scoped),因此 Scoped 服务默认在单个请求内唯一;
- 控制台项目需手动创建作用域(
CreateScope()),否则解析 Scoped 服务会抛出异常。
总结
- 核心概念:服务注册是将 "抽象服务类型 - 具体实现类型 - 生命周期" 绑定并存储到 DI 容器的过程,目的是解耦和自动管理实例;
- 核心操作 :通过
ServiceCollection的AddTransient/AddScoped/AddSingleton完成注册,通过ServiceProvider解析服务; - 生命周期关键:瞬时(每次新实例)、作用域(同作用域唯一)、单例(全局唯一),需根据业务场景选择。