- 语言运行时
- 标准库
- JIT编译
- 编译器
【免费下载链接】runtime
.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
Microsoft.Extensions.DependencyInjection是 .NET 官方提供的高性能依赖注入(DI)容器实现,位于本仓库 src/libraries/Microsoft.Extensions.DependencyInjection 目录下,它实现了Microsoft.Extensions.DependencyInjection.Abstractions包中定义的全部 DI 接口,是 ASP.NET Core、.NET Generic Host 以及绝大多数现代 .NET 应用实现控制反转(IoC)的基石。读完本文,你将掌握服务的注册、解析、生命周期管理、作用域(Scope)与校验配置等全套实战技能,并能从源码层面理解ServiceProvider的编译式解析引擎与内建服务的工作原理。
一、包定位:它解决什么问题
依赖注入(Dependency Injection,DI)是一种软件设计模式,其核心目的是实现类与其依赖之间的控制反转(Inversion of Control,IoC):一个类不再自行new出它需要的依赖,而是通过构造函数、属性或方法由外部容器注入,从而降低耦合、提升可测试性与可维护性。
本包(Microsoft.Extensions.DependencyInjection)正是这一模式的默认容器实现。正如包的 PACKAGE.md 所述,它的关键能力是:提供Microsoft.Extensions.DependencyInjection.Abstractions包中 DI 接口的具体实现。也就是说,你平时接触的IServiceCollection、IServiceProvider、IServiceScopeFactory等接口定义在 Abstractions 包中,而真正干活、负责创建和缓存实例的容器本体是这个包。
二、五分钟上手:从零构建一个 DI 容器
包的 PACKAGE.md 给出了最经典的入门示例,这里完整保留并展开说明:
ServiceCollection services = new (); services.AddSingleton<IMessageWriter, MessageWriter>(); using ServiceProvider provider = services.BuildServiceProvider(); // The code below, following the IoC pattern, is typically only aware of the IMessageWriter interface, not the implementation. IMessageWriter messageWriter = provider.GetService<IMessageWriter>()!; messageWriter.Write("Hello"); public interface IMessageWriter { void Write(string message); } internal class MessageWriter : IMessageWriter { public void Write(string message) { Console.WriteLine($"MessageWriter.Write(message: \"{message}\")"); } }这段代码演示了 DI 的完整三步骤,也是整个模式的精髓:
- 创建注册集合:
new ServiceCollection()创建一个服务描述符(ServiceDescriptor)集合,ServiceCollection类的实现在 ServiceCollection.cs。 - 注册服务:
services.AddSingleton<IMessageWriter, MessageWriter>()将抽象接口IMessageWriter与具体实现MessageWriter建立映射,并声明其生命周期为Singleton。这个扩展方法定义于 Abstractions 包的 ServiceCollectionServiceExtensions.cs,除AddSingleton外还有AddScoped、AddTransient等重载。 - 构建并解析:
BuildServiceProvider()将集合编译为ServiceProvider;业务代码只依赖IMessageWriter接口,对MessageWriter的具体实现完全无感——这正是 IoC 的核心价值:面向抽象编程,实现可随时替换(例如换成 Mock 或日志代理)。
2.1 关于!(null 容忍)的说明
示例中provider.GetService<IMessageWriter>()!的!是 null-forgiving 运算符,因为GetService的签名允许返回null(服务未注册时)。如果你确定服务一定存在,更推荐使用GetRequiredService<T>()——它会在服务缺失时抛出InvalidOperationException("No service registered for type..."),详见 ServiceProviderServiceExtensions.cs。
三、核心类型解析:容器是如何运转的
PACKAGE.md 明确列出了本库的三个主要类型,下面逐一结合源码深挖:
3.1 DefaultServiceProviderFactory
实现 DefaultServiceProviderFactory.cs:
public class DefaultServiceProviderFactory : IServiceProviderFactory<IServiceCollection> { private readonly ServiceProviderOptions _options; public DefaultServiceProviderFactory() : this(ServiceProviderOptions.Default) { } public DefaultServiceProviderFactory(ServiceProviderOptions options) { _options = options ?? throw new ArgumentNullException(nameof(options)); } public IServiceCollection CreateBuilder(IServiceCollection services) => services; public IServiceProvider CreateServiceProvider(IServiceCollection containerBuilder) => containerBuilder.BuildServiceProvider(_options); }它实现了 Abstractions 包中的 IServiceProviderFactory.cs 接口。IServiceProviderFactory<TContainerBuilder>是 .NET Generic Host 与第三方容器(如 Autofac)集成的标准抽象:宿主框架(如Microsoft.Extensions.Hosting)调用CreateBuilder获得容器构建器,应用在其中注册服务,最后宿主调用CreateServiceProvider生成最终的IServiceProvider。默认实现中CreateBuilder直接返回原集合(因为默认容器本身就是IServiceCollection),而CreateServiceProvider则委托给BuildServiceProvider(_options)。
3.2 ServiceCollectionContainerBuilderExtensions
定义于 ServiceCollectionContainerBuilderExtensions.cs,提供三个BuildServiceProvider重载:
public static ServiceProvider BuildServiceProvider(this IServiceCollection services) => BuildServiceProvider(services, ServiceProviderOptions.Default); public static ServiceProvider BuildServiceProvider(this IServiceCollection services, bool validateScopes) => services.BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = validateScopes }); public static ServiceProvider BuildServiceProvider(this IServiceCollection services, ServiceProviderOptions options) { ArgumentNullException.ThrowIfNull(services); ArgumentNullException.ThrowIfNull(options); return new ServiceProvider(services, options); }三个重载最终都汇聚到new ServiceProvider(services, options)。注意它返回的是具体类型ServiceProvider而非接口,因此你可以直接使用ServiceProvider上特有的方法(如GetKeyedService、DisposeAsync)。
3.3 ServiceProvider:默认容器本体
ServiceProvider.cs 是容器的核心实现类:
public sealed class ServiceProvider : IServiceProvider, IKeyedServiceProvider, IDisposable, IAsyncDisposable它实现了四个接口:
IServiceProvider:提供GetService(Type)基础解析能力;IKeyedServiceProvider:支持 .NET 8+ 的键控服务(Keyed Service),可通过GetKeyedService(Type, object? key)按键解析(见 IKeyedServiceProvider.cs);IDisposable/IAsyncDisposable:负责释放容器及所有由它创建的可释放服务。
构造时的关键动作
在 构造函数 中,容器依次完成:
- 创建根作用域
Root(ServiceProviderEngineScope),它代表容器的最顶层作用域; - 选择解析引擎
GetEngine()——根据运行时是否支持动态代码(RuntimeFeature.IsDynamicCodeSupported)以及Microsoft.Extensions.DependencyInjection.DisableDynamicEngine开关,在表达式树引擎、IL 发射引擎与运行时解释引擎之间选择(详见下文第五节); - 建立 CallSiteFactory:将注册的服务描述符编译成"调用点(CallSite)"工厂;
- 注册四类内建服务,即使你没显式注册它们也始终可用:
IServiceProvider→ServiceProviderCallSite;IServiceScopeFactory→ 根作用域本身;IServiceProviderIsService/IServiceProviderIsKeyedService→ 用于查询某服务是否已注册(对应 IServiceProviderIsService.cs);
- 执行可选校验:若
options.ValidateScopes开启,则创建CallSiteValidator用于运行期作用域校验;若options.ValidateOnBuild开启,则在构建时遍历所有描述符逐个验证能否被构造,失败则聚合抛出AggregateException("Some services are not able to be constructed", ...)。
解析流程
GetService(Type)内部通过ConcurrentDictionary<ServiceIdentifier, ServiceAccessor>做按需编译 + 缓存:同一个服务第一次被请求时,创建并编译其ServiceAccessor,后续请求直接复用缓存的委托,避免重复构建。解析前后还会分别触发OnCreate(调用点校验)与OnResolve(作用域校验),并通过DependencyInjectionEventSource记录ServiceProviderBuilt、ServiceResolved、ServiceProviderDisposed等诊断事件(见 DependencyInjectionEventSource.cs)。
四、生命周期(Lifetime):Singleton / Scoped / Transient
生命周期由 Abstractions 包中的 ServiceLifetime.cs 枚举定义:
| 生命周期 | 行为 | 典型场景 |
|---|---|---|
Singleton | 整个容器只创建一个实例,首次解析时创建,之后所有请求共享同一实例 | 配置对象、日志器、无状态服务 |
Scoped | 每个作用域(Scope)创建一个实例,同一作用域内共享 | ASP.NET Core 中每个 HTTP 请求一个作用域,故DbContext、IUnitOfWork常用 Scoped |
Transient | 每次解析都创建新实例 | 轻量、无状态、线程不安全的短命服务 |
对应注册方法为AddSingleton<TService, TImpl>()、AddScoped<TService, TImpl>()、AddTransient<TService, TImpl>(),均定义在 ServiceCollectionServiceExtensions.cs。
4.1 作用域的正确用法
作用域(Scope)通过IServiceScopeFactory创建,最标准的模式是:
using ServiceProvider provider = services.BuildServiceProvider(); using IServiceScope scope = provider.CreateScope(); // 创建子作用域 IMessageWriter writer = scope.ServiceProvider.GetRequiredService<IMessageWriter>();IServiceScopeFactory与IServiceScope的接口定义分别在 IServiceScopeFactory.cs 和 IServiceScope.cs,其默认实现ServiceProviderEngineScope位于 ServiceLookup/ServiceProviderEngineScope.cs。
重要约束(Captive Dependency 反模式):Singleton服务不能依赖Scoped服务,否则该 Scoped 服务会被"囚禁"在单例中,其作用域语义完全失效。ValidateScopes正是用来在开发期捕获这类错误的(见第五节)。
4.2 释放语义
ServiceProvider实现了IDisposable与IAsyncDisposable,其 Dispose 与 DisposeAsync 实现 会级联释放由容器创建的所有IDisposable/IAsyncDisposable实例(容器自行管理的 Singleton 与 Scope 内实例)。特别提醒:
- 当服务同时实现
IAsyncDisposable和IDisposable时,同步Dispose()会先调用DisposeAsync并同步等待其完成; - 若服务只实现
IAsyncDisposable而未实现IDisposable,同步Dispose()会抛出InvalidOperationException,此时必须改用DisposeAsync()——这正是 ServiceProvider.cs 的 XML 注释中"Prefer callingDisposeAsync"的原因。
因此通用最佳实践是:只要容器可能承载异步可释放服务,就优先使用await using+DisposeAsync。
五、容器配置:ServiceProviderOptions 与运行期校验
ServiceProviderOptions.cs 是容器的配置入口,包含两个开关:
| 属性 | 默认值 | 作用 |
|---|---|---|
ValidateScopes | false | 校验Scoped 服务不会被从根作用域(Root Provider)解析。开启后,一旦从根容器直接解析 Scoped 服务即抛异常,可有效阻止 Captive Dependency |
ValidateOnBuild | false | 在BuildServiceProvider时预先验证所有已注册服务能否被成功构造(依赖链是否完整、构造函数是否可满足),失败立即抛出AggregateException;开放泛型(Open Generic)服务不参与该校验(见 ServiceProviderOptions.cs 的注释) |
注意源码中internal static readonly ServiceProviderOptions Default是一个复用单例,注释明确写着 "Avoid allocating objects in the default case"——默认路径不分配新对象,这也是容器性能优化的一环。
5.1 开启校验的两种方式
// 方式一:bool 重载(开发环境常用) using ServiceProvider provider = services.BuildServiceProvider(validateScopes: true); // 方式二:完整选项(推荐,可同时开启两项) var options = new ServiceProviderOptions { ValidateScopes = true, ValidateOnBuild = true }; using ServiceProvider provider = services.BuildServiceProvider(options);实践中,ValidateScopes = true通常只应在开发环境开启(ASP.NET Core 的Development环境默认开启),生产环境关闭以省去每层解析前的校验开销。
5.2 作用域校验的底层实现
从 ServiceProvider.cs 可以看到,校验是通过CallSiteValidator挂钩的:OnCreate在调用点(CallSite)构建时执行ValidateCallSite,OnResolve在每次解析时执行ValidateResolution(callSite, scope, Root),传入当前作用域与根作用域做对比。CallSiteValidator的实现位于 ServiceLookup/CallSiteValidator.cs,它会沿服务依赖链回溯,检查是否存在"Scoped 服务被 Singleton 依赖"或"Scoped 服务从根解析"的非法组合。
5.3 宿主中的等价配置
若使用Microsoft.Extensions.Hosting(相关包)而非手动构建容器,上述选项由HostBuilder的配置统一管理,效果与直接传ServiceProviderOptions完全等价——这正是DefaultServiceProviderFactory存在的意义:它把"容器如何构建"从宿主框架中解耦出来,宿主只需要知道IServiceProviderFactory<IServiceCollection>这一抽象即可。
六、解析引擎内幕:三层编译优化架构
本包之所以以高性能著称,源自 ServiceLookup 目录下的多引擎架构。抽象基类 ServiceProviderEngine.cs 只有一个抽象方法:
public abstract Func<ServiceProviderEngineScope, object?> RealizeService(ServiceCallSite callSite);即在拿到某个服务的"调用点"后,产出一个直接执行解析的委托。三个具体引擎位于同一目录:
- Expressions/ExpressionsServiceProviderEngine.cs:基于
System.Linq.Expressions表达式树,把解析过程编译成强类型委托; - ILEmit/ILEmitServiceProviderEngine.cs:基于
System.Reflection.Emit动态发射 IL,性能最高,但要求运行时支持动态代码; DynamicServiceProviderEngine与RuntimeServiceProviderEngine:前者在支持动态代码时包装编译型引擎,后者是纯解释执行的兜底路径(适用于 AOT、NativeAOT 等禁用动态代码的场景)。
引擎选择的决策点体现在 ServiceProvider.cs:通过AppContext开关Microsoft.Extensions.DependencyInjection.DisableDynamicEngine和RuntimeFeature.IsDynamicCodeSupported判定。在 NativeAOT 场景下(仓库中 src/coreclr/nativeaot 支持的目标环境),无法动态发射 IL,容器自动退化为RuntimeServiceProviderEngine,保证功能一致。
围绕引擎工作的是CallSiteFactory(CallSiteFactory.cs):它负责把每个ServiceDescriptor展开为对应的调用点类型——构造函数调用(ConstructorCallSite)、工厂委托(FactoryCallSite)、常量(ConstantCallSite)、IEnumerable<T>批量注入(IEnumerableCallSite)以及ServiceProvider本身(ServiceProviderCallSite),并用CallSiteChain检测循环依赖(CallSiteChain.cs)。ServiceCallSite的类层次定义于 ServiceCallSite.cs。
七、键控服务(Keyed Services):.NET 8+ 的按名解析
ServiceProvider实现了IKeyedServiceProvider,意味着本容器原生支持键控服务。注册与解析的扩展方法分布在 Abstractions 包的键控专用文件中:
- 注册:
AddKeyedSingleton<TService, TImpl>(object? serviceKey)、AddKeyedScoped、AddKeyedTransient,见 ServiceCollectionServiceExtensions.Keyed.cs; - 解析:
GetKeyedService<T>(object? serviceKey)、GetRequiredKeyedService<T>(object? serviceKey),见 ServiceProviderKeyedServiceExtensions.cs; - 注入:构造函数参数上加
[FromKeyedServices("key")]特性(FromKeyedServicesAttribute.cs),或用KeyedService.AnyKey一次性解析某类型的全部键控实现。
services.AddKeyedSingleton<IMessageWriter, ConsoleWriter>("console"); services.AddKeyedSingleton<IMessageWriter, FileWriter>("file"); using ServiceProvider provider = services.BuildServiceProvider(); IMessageWriter writer = provider.GetRequiredKeyedService<IMessageWriter>("console");ServiceIdentifier(ServiceLookup/ServiceIdentifier.cs)将(serviceKey, serviceType)组合作为缓存与解析的键,键控与非键控服务在容器内部统一建模。
八、关联包与生态定位
PACKAGE.md 列出了三个关联包,理清它们的关系有助于理解整个 DI 生态:
Microsoft.Extensions.DependencyInjection.Abstractions:纯接口与抽象层(IServiceCollection、IServiceProvider、ServiceDescriptor、ServiceLifetime等),不含任何实现。本仓库中其源码位于 src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions,该包自身的 PACKAGE.md 定义了 DI 的抽象契约。Microsoft.Extensions.Hosting:构建带生命周期管理(IHostedService后台任务、日志、配置)的应用程序宿主,其内部正是通过DefaultServiceProviderFactory使用本包作为默认容器;源码位于 src/libraries/Microsoft.Extensions.Hosting。Microsoft.Extensions.Options:基于本容器实现强类型配置绑定(IOptions<T>、IOptionsMonitor<T>),把appsettings.json等配置源映射为可注入的服务。
这三者构成 .NET 应用"配置 + DI + 托管"的黄金三角,而本包就是其中最核心的"装配工厂"。
九、总结:本包在仓库中的位置与工程实践要点
Microsoft.Extensions.DependencyInjection包源码位于 src/libraries/Microsoft.Extensions.DependencyInjection/src,作为 .NET 运行时仓库(README 位于 README.md)的一部分,它随 .NET 一起开源(MIT 许可,见 LICENSE.TXT),与System.Private.CoreLib等基础库共同构成 .NET 平台。
工程实践要点速查:
- 接口与实现分离:业务代码只依赖 Abstractions 中的接口,替换容器(如换成第三方容器)无需改业务代码;
- 生命周期选择:无状态共享用
Singleton,请求级状态用Scoped,轻量短命对象用Transient; - 开发期开启
ValidateScopes = true,快速暴露 Captive Dependency 与作用域滥用; - 生产环境默认配置:
BuildServiceProvider()走ServiceProviderOptions.Default,零额外分配与校验开销; - 优先
DisposeAsync:只要容器中可能存在异步可释放服务,就用await using; - 理解多引擎架构:动态代码可用时容器自动选择表达式树/IL 发射引擎获得最佳性能,AOT 场景自动降级为解释引擎,无需手动干预。
本文所有源码结论均可在仓库对应路径中找到原始实现,建议读者结合 ServiceProvider.cs、ServiceCollectionContainerBuilderExtensions.cs 与 ServiceLookup 目录继续深入阅读,理解一个生产级 DI 容器的完整设计。
- 语言运行时
- 标准库
- JIT编译
- 编译器
【免费下载链接】runtime
.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
相关推荐
LenovoLegionToolkit源码解析:IoC容器设计与依赖注入实现
LenovoLegionToolkit源码解析:IoC容器设计与依赖注入实现 引言 在现代软件工程中,控制反转(Inversion of Control, Io
桌面应用MaaAssistantArknights 安装教程:让明日方舟一键自动长草跑起来的 3 步
MaaAssistantArknights 安装教程:让明日方舟一键自动长草跑起来的 3 步 MaaAssistantArknights(下称 MAA)是一款《
计算机视觉GUI自动化RPA从依赖注入到解耦架构:Docmost中的NestJS IoC容器实践指南
从依赖注入到解耦架构:Docmost中的NestJS IoC容器实践指南 在现代后端开发中,依赖注入(Dependency Injection, DI)已成为构
后端前端内容协同知识库
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考