.NET 依赖注入容器 Microsoft.Extensions.DependencyInjection 完全指南:从 IoC 入门到 ServiceProvider 源码解析

发布时间:2026/9/20 13:25:16
.NET 依赖注入容器 Microsoft.Extensions.DependencyInjection 完全指南:从 IoC 入门到 ServiceProvider 源码解析 语言运行时标准库JIT编译编译器【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址https://gitcode.com/GitHub_Trending/runtime6/runtime点击查看免费下载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 InjectionDI是一种软件设计模式其核心目的是实现类与其依赖之间的控制反转Inversion of ControlIoC一个类不再自行new出它需要的依赖而是通过构造函数、属性或方法由外部容器注入从而降低耦合、提升可测试性与可维护性。本包Microsoft.Extensions.DependencyInjection正是这一模式的默认容器实现。正如包的 PACKAGE.md 所述它的关键能力是提供Microsoft.Extensions.DependencyInjection.Abstractions包中 DI 接口的具体实现。也就是说你平时接触的IServiceCollection、IServiceProvider、IServiceScopeFactory等接口定义在 Abstractions 包中而真正干活、负责创建和缓存实例的容器本体是这个包。二、五分钟上手从零构建一个 DI 容器包的 PACKAGE.md 给出了最经典的入门示例这里完整保留并展开说明ServiceCollection services new (); services.AddSingletonIMessageWriter, 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.GetServiceIMessageWriter()!; 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.AddSingletonIMessageWriter, MessageWriter()将抽象接口IMessageWriter与具体实现MessageWriter建立映射并声明其生命周期为Singleton。这个扩展方法定义于 Abstractions 包的 ServiceCollectionServiceExtensions.cs除AddSingleton外还有AddScoped、AddTransient等重载。构建并解析BuildServiceProvider()将集合编译为ServiceProvider业务代码只依赖IMessageWriter接口对MessageWriter的具体实现完全无感——这正是 IoC 的核心价值面向抽象编程实现可随时替换例如换成 Mock 或日志代理。2.1 关于!null 容忍的说明示例中provider.GetServiceIMessageWriter()!的!是 null-forgiving 运算符因为GetService的签名允许返回null服务未注册时。如果你确定服务一定存在更推荐使用GetRequiredServiceT()——它会在服务缺失时抛出InvalidOperationExceptionNo service registered for type...详见 ServiceProviderServiceExtensions.cs。三、核心类型解析容器是如何运转的PACKAGE.md 明确列出了本库的三个主要类型下面逐一结合源码深挖3.1 DefaultServiceProviderFactory实现 DefaultServiceProviderFactory.cspublic class DefaultServiceProviderFactory : IServiceProviderFactoryIServiceCollection { 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 接口。IServiceProviderFactoryTContainerBuilder是 .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.csIDisposable/IAsyncDisposable负责释放容器及所有由它创建的可释放服务。构造时的关键动作在 构造函数 中容器依次完成创建根作用域RootServiceProviderEngineScope它代表容器的最顶层作用域选择解析引擎GetEngine()——根据运行时是否支持动态代码RuntimeFeature.IsDynamicCodeSupported以及Microsoft.Extensions.DependencyInjection.DisableDynamicEngine开关在表达式树引擎、IL 发射引擎与运行时解释引擎之间选择详见下文第五节建立 CallSiteFactory将注册的服务描述符编译成调用点CallSite工厂注册四类内建服务即使你没显式注册它们也始终可用IServiceProvider→ServiceProviderCallSiteIServiceScopeFactory→ 根作用域本身IServiceProviderIsService/IServiceProviderIsKeyedService→ 用于查询某服务是否已注册对应 IServiceProviderIsService.cs执行可选校验若options.ValidateScopes开启则创建CallSiteValidator用于运行期作用域校验若options.ValidateOnBuild开启则在构建时遍历所有描述符逐个验证能否被构造失败则聚合抛出AggregateException(Some services are not able to be constructed, ...)。解析流程GetService(Type)内部通过ConcurrentDictionaryServiceIdentifier, ServiceAccessor做按需编译 缓存同一个服务第一次被请求时创建并编译其ServiceAccessor后续请求直接复用缓存的委托避免重复构建。解析前后还会分别触发OnCreate调用点校验与OnResolve作用域校验并通过DependencyInjectionEventSource记录ServiceProviderBuilt、ServiceResolved、ServiceProviderDisposed等诊断事件见 DependencyInjectionEventSource.cs。四、生命周期LifetimeSingleton / Scoped / Transient生命周期由 Abstractions 包中的 ServiceLifetime.cs 枚举定义生命周期行为典型场景Singleton整个容器只创建一个实例首次解析时创建之后所有请求共享同一实例配置对象、日志器、无状态服务Scoped每个作用域Scope创建一个实例同一作用域内共享ASP.NET Core 中每个 HTTP 请求一个作用域故DbContext、IUnitOfWork常用 ScopedTransient每次解析都创建新实例轻量、无状态、线程不安全的短命服务对应注册方法为AddSingletonTService, TImpl()、AddScopedTService, TImpl()、AddTransientTService, TImpl()均定义在 ServiceCollectionServiceExtensions.cs。4.1 作用域的正确用法作用域Scope通过IServiceScopeFactory创建最标准的模式是using ServiceProvider provider services.BuildServiceProvider(); using IServiceScope scope provider.CreateScope(); // 创建子作用域 IMessageWriter writer scope.ServiceProvider.GetRequiredServiceIMessageWriter();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 usingDisposeAsync。五、容器配置ServiceProviderOptions 与运行期校验ServiceProviderOptions.cs 是容器的配置入口包含两个开关属性默认值作用ValidateScopesfalse校验Scoped 服务不会被从根作用域Root Provider解析。开启后一旦从根容器直接解析 Scoped 服务即抛异常可有效阻止 Captive DependencyValidateOnBuildfalse在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构建时执行ValidateCallSiteOnResolve在每次解析时执行ValidateResolution(callSite, scope, Root)传入当前作用域与根作用域做对比。CallSiteValidator的实现位于 ServiceLookup/CallSiteValidator.cs它会沿服务依赖链回溯检查是否存在Scoped 服务被 Singleton 依赖或Scoped 服务从根解析的非法组合。5.3 宿主中的等价配置若使用Microsoft.Extensions.Hosting相关包而非手动构建容器上述选项由HostBuilder的配置统一管理效果与直接传ServiceProviderOptions完全等价——这正是DefaultServiceProviderFactory存在的意义它把容器如何构建从宿主框架中解耦出来宿主只需要知道IServiceProviderFactoryIServiceCollection这一抽象即可。六、解析引擎内幕三层编译优化架构本包之所以以高性能著称源自 ServiceLookup 目录下的多引擎架构。抽象基类 ServiceProviderEngine.cs 只有一个抽象方法public abstract FuncServiceProviderEngineScope, 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保证功能一致。围绕引擎工作的是CallSiteFactoryCallSiteFactory.cs它负责把每个ServiceDescriptor展开为对应的调用点类型——构造函数调用ConstructorCallSite、工厂委托FactoryCallSite、常量ConstantCallSite、IEnumerableT批量注入IEnumerableCallSite以及ServiceProvider本身ServiceProviderCallSite并用CallSiteChain检测循环依赖CallSiteChain.cs。ServiceCallSite的类层次定义于 ServiceCallSite.cs。七、键控服务Keyed Services.NET 8 的按名解析ServiceProvider实现了IKeyedServiceProvider意味着本容器原生支持键控服务。注册与解析的扩展方法分布在 Abstractions 包的键控专用文件中注册AddKeyedSingletonTService, TImpl(object? serviceKey)、AddKeyedScoped、AddKeyedTransient见 ServiceCollectionServiceExtensions.Keyed.cs解析GetKeyedServiceT(object? serviceKey)、GetRequiredKeyedServiceT(object? serviceKey)见 ServiceProviderKeyedServiceExtensions.cs注入构造函数参数上加[FromKeyedServices(key)]特性FromKeyedServicesAttribute.cs或用KeyedService.AnyKey一次性解析某类型的全部键控实现。services.AddKeyedSingletonIMessageWriter, ConsoleWriter(console); services.AddKeyedSingletonIMessageWriter, FileWriter(file); using ServiceProvider provider services.BuildServiceProvider(); IMessageWriter writer provider.GetRequiredKeyedServiceIMessageWriter(console);ServiceIdentifierServiceLookup/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基于本容器实现强类型配置绑定IOptionsT、IOptionsMonitorT把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.项目地址https://gitcode.com/GitHub_Trending/runtime6/runtime点击查看免费下载相关推荐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),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询