C#中的服务注册剖析

一、服务注册的核心概念

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:创建控制台项目(环境准备)
  1. 打开 Visual Studio/VS Code,创建.NET 8(或更高版本)控制台项目;

  2. 确保项目文件(.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:代码关键说明
  1. 服务注册核心

    • ServiceCollection是 "注册清单",通过AddXxx方法将服务类型与实现类型、生命周期绑定;
    • BuildServiceProvider()将注册清单转换为可解析服务的ServiceProvider(DI 容器)。
  2. 服务解析方法

    • GetRequiredService<T>():强制解析服务,若服务未注册则抛出异常(推荐);
    • GetService<T>():解析服务,若未注册则返回null
    • CreateScope():创建作用域,作用域内解析的 Scoped 服务实例唯一。
  3. 依赖注入方式

    • 示例中BusinessService通过构造函数注入 依赖的IUserServiceICacheService这是.NET DI 的默认且推荐的方式;
    • DI 容器会自动解析依赖链:解析IBusinessService时,先解析其构造函数中的IUserServiceICacheService,再创建BusinessService实例。
步骤 5:运行结果分析

运行程序后,会看到以下关键输出:

  • 瞬时服务 :两次解析的IUserService实例 ID 完全不同;
  • 作用域服务 :同一个作用域内两次解析的IOrderService实例 ID 相同,不同作用域 ID 不同;
  • 单例服务 :无论直接解析还是在作用域内解析,ICacheService的实例 ID 始终相同,且缓存数据可复用;
  • 依赖注入BusinessService创建时,DI 容器自动注入依赖的服务实例,无需手动new

四、扩展知识点

  1. 服务注册的其他方式
    • 泛型服务注册:services.AddTransient(typeof(IGenericService<>), typeof(GenericService<>))
    • 工厂模式注册:services.AddTransient<IUserService>(sp => new UserService("自定义参数"))
  2. 服务生命周期注意事项
    • 禁止在 "短生命周期服务" 中注入 "长生命周期服务"(如 Scoped 服务注入 Singleton 服务),会导致 Scoped 服务被 "提升" 为 Singleton,引发线程安全问题;
    • 瞬时服务可注入任意生命周期的服务,单例服务只能注入单例服务。
  3. 控制台项目 vs Web 项目
    • Web 项目中,每个 HTTP 请求对应一个作用域(Scoped),因此 Scoped 服务默认在单个请求内唯一;
    • 控制台项目需手动创建作用域(CreateScope()),否则解析 Scoped 服务会抛出异常。

总结

  1. 核心概念:服务注册是将 "抽象服务类型 - 具体实现类型 - 生命周期" 绑定并存储到 DI 容器的过程,目的是解耦和自动管理实例;
  2. 核心操作 :通过ServiceCollectionAddTransient/AddScoped/AddSingleton完成注册,通过ServiceProvider解析服务;
  3. 生命周期关键:瞬时(每次新实例)、作用域(同作用域唯一)、单例(全局唯一),需根据业务场景选择。
相关推荐
张人玉2 小时前
c#DataTable类
数据库·c#
缺点内向2 小时前
如何在 C# .NET 中将 Markdown 转换为 PDF 和 Excel:完整指南
pdf·c#·.net·excel
CodeCraft Studio2 小时前
Excel处理控件Aspose.Cells教程:使用C#在Excel中创建旭日图
c#·excel·aspose·excel旭日图·excel库·excel开发控件·excel api库
民乐团扒谱机2 小时前
【微实验】仿AU音频编辑器开发实践:从零构建音频可视化工具
算法·c#·仿真·audio·fft·频谱
武藤一雄3 小时前
彻底吃透.NET中序列化反序列化
xml·微软·c#·json·.net·.netcore
one9964 小时前
C# 的进程间通信(IPC,Inter-Process Communication)
开发语言·c#
CreasyChan4 小时前
unity-向量数学:由浅入深详解
unity·c#
专注VB编程开发20年14 小时前
C#全面超越JAVA,主要还是跨平台用的人少
java·c#·.net·跨平台
小猪快跑爱摄影17 小时前
【AutoCad 2025】【C#】零基础教程(四)——MText 常见属性
c#·autocad