发布于2026-07-23 阅读(0)
扫一扫,手机访问
IServiceCollection:服务注册的“容器”,用来存放你声明的各种服务。IServiceProvider:服务的“解析器”,你需要什么服务,找它要就行。ServiceDescriptor:服务的“身份证”,里面记录了服务的类型、实现类型以及它的生命周期信息。生命周期是DI里一个绕不开的话题。对于同一个服务,不同生命周期的行为差别很大。下面这张表帮你快速理清,这三者到底对应什么场景。
| 生命周期 | 描述 | 适用场景 |
|---|---|---|
| Transient | 每次请求都会创建一个全新的实例 | 轻量级、无状态的服务 |
| Scoped | 同一个作用域内(比如一个Web请求)共享同一个实例 | Web请求相关的上下文 |
| Singleton | 全局范围内,只有一个实例,一直复用 | 配置服务、缓存等全局共享的数据 |
// 注册示例services.AddTransient();services.AddScoped ();services.AddSingleton ();
所以说,选择合适的生命周期,可不是随便选选就算了的。用错了,轻则性能下降,重则出现数据混乱的问题。
注册的底层逻辑其实很简单,就是把服务的类型、实现类型、生命周期封装成一个描述符,然后放进一个大“集合”里。
public static IServiceCollection AddTransient(this IServiceCollection services){ // 创建服务描述符 var descriptor = new ServiceDescriptor( typeof(TService), typeof(TImplementation), ServiceLifetime.Transient); // 添加到集合 services.Add(descriptor); return services;}
从容器里拿出一个服务时,背后经历了一个标准的查找和实例化流程。
public object GetService(Type serviceType){ // 1. 查找服务描述符 var descriptor = _descriptors.FirstOrDefault(d => d.ServiceType == serviceType); // 2. 根据生命周期创建实例 if (descriptor.Lifetime == ServiceLifetime.Singleton) { if (_singletons.TryGetValue(serviceType, out var instance)) return instance; instance = CreateInstance(descriptor); _singletons[serviceType] = instance; return instance; } // ...处理Scoped和Transient}除了最基础的直接注册,还有几种更灵活的方式,在实际开发中经常用到。
services.AddTransient(provider => { var otherService = provider.GetRequiredService (); return new ServiceImpl(otherService, "参数");});
services.AddTransient(typeof(IRepository<>), typeof(Repository<>));
// 注册多个实现services.AddTransient();services.AddTransient ();// 解析时获取所有实现var services = provider.GetServices ();
上面这几种模式,在应对不同业务策略或条件分支时,非常实用。
在ASP.NET Core里,DI可以说是无处不在,比如控制器、视图、甚至中间件,都可以直接用依赖注入来获取服务。
public class HomeController : Controller{ private readonly ILogger _logger; public HomeController(ILogger logger) { _logger = logger; // 自动注入 }} @inject IConfiguration Config当前环境: @Config["Environment"]
public class CustomMiddleware{ private readonly RequestDelegate _next; private readonly ILogger _logger; public CustomMiddleware( RequestDelegate next, ILogger logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { // 使用注入的服务 _logger.LogInformation("中间件执行"); await _next(context); }} 学习DI最好的方式,就是自己动手实现一个简化版。这能帮你真正想明白注册和解析的背后发生了什么。
public class SimpleContainer : IServiceProvider{ private readonly Dictionary _descriptors; public SimpleContainer(IEnumerable descriptors) { _descriptors = descriptors.ToDictionary(x => x.ServiceType); } public object GetService(Type serviceType) { if (!_descriptors.TryGetValue(serviceType, out var descriptor)) return null; if (descriptor.ImplementationInstance != null) return descriptor.ImplementationInstance; var type = descriptor.ImplementationType ?? descriptor.ServiceType; return ActivatorUtilities.CreateInstance(this, type); }} public static class PropertyInjectionExtensions{ public static void AddPropertyInjection(this IServiceCollection services) { services.AddTransient(); }}public class PropertyInjectionStartupFilter : IStartupFilter{ public Action Configure(Action next) { return builder => { builder.Use(async (context, nextMiddleware) => { var endpoint = context.GetEndpoint(); if (endpoint?.Metadata.GetMetadata() is { } descriptor) { var controller = context.RequestServices.GetRequiredService(descriptor.ControllerTypeInfo); // 反射实现属性注入 InjectProperties(controller, context.RequestServices); } await nextMiddleware(); }); next(builder); }; } private void InjectProperties(object target, IServiceProvider services) { var properties = target.GetType().GetProperties() .Where(p => p.CanWrite && p.GetCustomAttribute() != null); foreach (var prop in properties) { var service = services.GetService(prop.PropertyType); if (service != null) prop.SetValue(target, service); } }} 这里有个很经典的坑:把Scoped服务“捕获”到Singleton里。这样做会导致Scoped服务被长时间持有,产生意料之外的生命周期错乱。
// 错误示例:捕获Scoped服务到Singleton中services.AddSingleton(provider => { var scopedService = provider.GetRequiredService (); // 危险! return new BackgroundService(scopedService);});// 正确做法:使用IServiceScopeFactoryservices.AddSingleton (provider => { var scopeFactory = provider.GetRequiredService (); return new BackgroundService(scopeFactory);});
有时候一个构造函数里塞了七八个参数,这其实是不好的设计。一个类如果依赖太多东西,说明它的职责可能过重了。
// 不好:注入过多服务public class OrderService( ILogger logger, IEmailService emailService, ISmsService smsService, IRepository repo, ICache cache, IConfig config){ // ...}// 改进:使用聚合服务public class OrderService( ILogger logger, INotificationService notification, IOrderInfrastructure infra){ // ...}对于性能要求极高的场景,还可以考虑使用Source Generator在编译阶段就生成注册代码,避免运行时反射带来的性能开销。
[RegisterTransient(typeof(IMyService))]public class MyService : IMyService{ // ...}// 使用Source Generator自动生成注册代码static partial class ServiceRegistration{ static partial void AddGeneratedServices(IServiceCollection services) { services.AddTransient(); }} 总的来说,.NET的依赖注入系统是框架的核心基础设施。只有真正理解它的原理和实现方式,才能编写出更可测试、更松耦合的应用程序。从基础概念到高级用法,再到实际开发中的注意事项,都能帮你少走很多弯路。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8