一、服务注册的核心概念
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 安装):<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 个不同生命周期的服务:
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:服务注册与解析
编写主程序,完成服务注册、不同生命周期的解析验证:
/// <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解析服务; - 生命周期关键:瞬时(每次新实例)、作用域(同作用域唯一)、单例(全局唯一),需根据业务场景选择。