news 2026/5/27 4:49:18

IOC 容器 H.Iocable

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
IOC 容器 H.Iocable

IOC 容器H.Iocable

位置:Source/Providers/H.Iocable

核心文件:

文件作用
Ioc.cs全局 IOC 容器入口。
IocExtension.cs允许在 XAML 中通过 MarkupExtension 获取服务。
IocBindable.cs可绑定的 IOC 对象。
IocThrowIfNone.cs服务不存在时的异常处理。

Ioc本质是对Microsoft.Extensions.DependencyInjection的封装。典型流程:

ServiceCollectionsc=newServiceCollection();ConfigureServices(sc);Ioc.Build(sc);

获取服务:

varservice=Ioc.GetService<IMyService>();

常见注册方式:

services.AddOptions();services.TryAdd(ServiceDescriptor.Singleton<IMyService,MyService>());services.Configure(newAction<MyOptions>(setupAction));

学习重点:

  • AddXXX通常表示注册服务。
  • UseXXX通常表示启用配置或加入设置系统。
  • TryAdd避免覆盖用户自定义服务。
  • Singleton表示全局单例。
  • Options用于模块配置。

H.Iocable IOC 容器详解

一、什么是 IOC

IOC(Inversion of Control,控制反转)是一种设计模式,核心思想是:

将对象的创建和依赖管理交给容器,而不是由代码直接控制。

简单来说,就是对象不再自己创建依赖,而是由容器注入

二、H.Iocable 核心组件

2.1 组件架构

┌─────────────────────────────────────────────────────────────┐ │ H.Iocable │ ├─────────────────────────────────────────────────────────────┤ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ Ioc.cs │ │ IocExtension │ │ │ │ 全局容器入口 │ │ XAML 绑定扩展 │ │ │ └────────┬────────┘ └─────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ IocBindable │ │ Ioc<T>基类 │ │ │ │ 可绑定的IOC对象 │ │ 泛型单例封装 │ │ │ └─────────────────┘ └─────────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Microsoft.Extensions.DependencyInjection │ │ (.NET 原生 DI 框架) │ └─────────────────────────────────────────────────────────────┘

2.2 文件职责说明

文件核心作用使用场景
Ioc.cs全局 IOC 容器入口,提供服务获取和管理C# 代码中获取服务
IocExtension.csXAML 中通过标记扩展获取服务XAML 绑定服务
IocBindable.cs可绑定的 IOC 对象,实现 INotifyPropertyChangedMVVM 绑定场景
Ioc<T>泛型单例封装基类简化单例服务访问

三、核心实现剖析

3.1 Ioc.cs - 全局容器入口

这是整个 IOC 系统的核心,让我们一步步拆解:

publicstaticclassIoc{// 核心字段:服务提供者和服务集合privatestaticIServiceProvider_services=null;privatestaticIServiceCollection_serviceCollection=null;publicstaticIServiceProviderServices=>_services;// 构建容器:将 ServiceCollection 转换为 ServiceProviderpublicstaticvoidBuild(IServiceCollectionserviceCollection){_services=serviceCollection.BuildServiceProvider();_serviceCollection=serviceCollection;}// 获取服务的两种方式publicstaticTGetService<T>(boolthrowIfNone=true){// 容器未初始化时的处理if(_services==null)returnthrowIfNone?thrownewArgumentNullException(...):default;returnGetService<T>(typeof(T),throwIfNone);}publicstaticTGetService<T>(Typetype,boolthrowIfNone=true){Tr=(T)_services.GetService(type);// 服务不存在时的异常处理if(r==null&&throwIfNone)thrownewArgumentNullException(typeof(T).FullName,$"此接口为依赖注入接口,请先在ApplicationBase中注册<{typeof(T).Name}>服务");returnr;}// 检查服务是否存在publicstaticboolExist<T>(){returnGetService<T>(throwIfNone:false)!=null;}// 批量获取实现某个接口的所有服务publicstaticIEnumerable<T>GetAssignableFromServices<T>(Func<T,bool>predicate=null){foreach(ServiceDescriptoritemin_serviceCollection){if(typeof(T).IsAssignableFrom(item.ServiceType)){// 处理实例和工厂两种注册方式if(item.ImplementationInstance==null){// 从容器中获取实例foreach(objectinstanceinServices.GetServices(item.ServiceType)){if(instanceisTit&&predicate?.Invoke(it)!=false)yieldreturnit;}}else{// 直接使用已注册的实例if(item.ImplementationInstanceisTt&&predicate?.Invoke(t)!=false)yieldreturn(T)item.ImplementationInstance;}}}}}

3.2 IocExtension.cs - XAML 中使用 IOC

publicclassIocExtension:MarkupExtension{publicTypeType{get;set;}publicoverrideobjectProvideValue(IServiceProviderserviceProvider){// 在 XAML 解析时获取服务returnIoc.GetService<object>(this.Type);}}

3.3 IocBindable.cs - 可绑定的 IOC 对象

publicabstractclassIocBindable<T,Interface>:Ioc<T,Interface>,INotifyPropertyChangedwhereT:class,Interface,new(){publiceventPropertyChangedEventHandlerPropertyChanged;publicvirtualvoidRaisePropertyChanged([CallerMemberName]stringpropertyName=""){PropertyChanged?.Invoke(this,newPropertyChangedEventArgs(propertyName));}}

3.4 Ioc 泛型基类

// 带实现类型的泛型基类publicabstractclassIoc<T,Interface>whereT:class,Interface{publicstaticTInstance=>Ioc.GetService<Interface>()asT;}// 简化版泛型基类publicabstractclassIoc<Interface>{publicstaticInterfaceInstance=>Ioc.GetService<Interface>(false);}

四、完整使用流程

4.1 第一步:注册服务

App.xaml.csConfigureServices方法中注册:

publicpartialclassApp:ApplicationBase{protectedoverridevoidConfigureServices(IServiceCollectionservices){// 1. 注册选项服务services.AddOptions();// 2. 注册单例服务(推荐使用 TryAdd 避免覆盖)services.TryAdd(ServiceDescriptor.Singleton<IMyService,MyService>());// 3. 注册配置选项services.Configure<MyOptions>(options=>{options.Name="默认名称";options.MaxCount=100;});// 4. 使用扩展方法注册(框架推荐方式)services.AddHome<ProjectHomeViewPresenter>();services.AddProject<AIDIProjectService>();}}

4.2 第二步:容器构建

ApplicationBase会自动完成这一步:

protectedvoidInitServiceCollection(){ServiceCollectionsc=newServiceCollection();this.ConfigureServices(sc);// 调用子类的注册Ioc.Build(sc);// 构建容器// 容器构建后即可使用Ioc.GetService<ILoadGlobalizationOptionsService>(false)?.Load(outstringmessage);}

4.3 第三步:获取服务

方式一:直接获取
// 获取服务(服务不存在时抛异常)varlogService=Ioc.GetService<ILogService>();// 安全获取(服务不存在时返回 null)varoptionalService=Ioc.GetService<IMyOptionalService>(throwIfNone:false);if(optionalService!=null){optionalService.DoSomething();}
方式二:批量获取
// 获取所有实现了 ISplashLoadable 接口的服务IEnumerable<ISplashLoadable>loads=Ioc.GetAssignableFromServices<ISplashLoadable>();foreach(varloadinloads){load.Load(outstringmessage);}
方式三:使用泛型基类
// 定义服务访问类publicclassIocLogService:Ioc<ILogService>{}// 使用时直接访问 Instance 属性IocLogService.Instance.Info("系统启动");
方式四:XAML 中使用
<Windowx:Class="MyApp.MainWindow"xmlns:local="clr-namespace:H.Iocable"><!-- 通过 IocExtension 获取服务并绑定 --><ContentControlContent="{local:IocExtension Type={x:Type local:IMyPresenter}}"/></Window>

五、服务注册方式详解

5.1 生命周期对比

生命周期说明使用场景
Singleton全局唯一实例日志服务、配置服务、全局状态管理
Scoped每个作用域一个实例数据库上下文、事务处理
Transient每次获取新实例轻量级服务、无状态服务

5.2 注册方法对比

// 基础注册(会覆盖已注册的服务)services.AddSingleton<IMyService,MyService>();services.AddScoped<IMyService,MyService>();services.AddTransient<IMyService,MyService>();// 安全注册(不会覆盖已注册的服务)services.TryAdd(ServiceDescriptor.Singleton<IMyService,MyService>());// 实例注册(直接注册已创建的实例)services.AddSingleton<IMyService>(newMyService());// 工厂注册(延迟创建)services.AddSingleton<IMyService>(sp=>{vardependency=sp.GetService<IDependency>();returnnewMyService(dependency);});

5.3 扩展方法注册模式

框架推荐使用扩展方法封装注册逻辑:

publicstaticclassMyModuleExtension{publicstaticIServiceCollectionAddMyModule(thisIServiceCollectionservices,Action<MyOptions>setupAction=null){// 1. 添加选项支持services.AddOptions();// 2. 注册核心服务services.TryAddSingleton<IMyService,MyService>();// 3. 注册配置选项if(setupAction!=null)services.Configure(setupAction);// 4. 链式返回returnservices;}}// 使用时services.AddMyModule(options=>{options.Name="自定义名称";});

六、实际应用案例

6.1 案例一:日志服务

// 定义接口publicinterfaceILogService{voidInfo(stringmessage);voidError(Exceptionex);}// 实现类publicclassLogService:ILogService{publicvoidInfo(stringmessage)=>Console.WriteLine($"INFO:{message}");publicvoidError(Exceptionex)=>Console.WriteLine($"ERROR:{ex.Message}");}// 注册services.AddSingleton<ILogService,LogService>();// 使用Ioc.GetService<ILogService>().Info("应用启动");

6.2 案例二:配置选项

// 定义配置类publicclassAppOptions{publicstringTitle{get;set;}="My App";publicintMaxItems{get;set;}=10;}// 注册配置services.Configure<AppOptions>(options=>{options.Title="WPF Control App";options.MaxItems=100;});// 获取配置varoptions=Ioc.GetService<IOptions<AppOptions>>().Value;Console.WriteLine(options.Title);

6.3 案例三:批量加载服务

// 定义接口publicinterfaceISplashLoadable{stringName{get;}boolLoad(outstringmessage);}// 多个实现publicclassSettingLoader:ISplashLoadable{...}publicclassThemeLoader:ISplashLoadable{...}publicclassDataLoader:ISplashLoadable{...}// 注册services.AddSingleton<ISplashLoadable,SettingLoader>();services.AddSingleton<ISplashLoadable,ThemeLoader>();services.AddSingleton<ISplashLoadable,DataLoader>();// 批量获取并执行varloaders=Ioc.GetAssignableFromServices<ISplashLoadable>();foreach(varloaderinloaders){loader.Load(outstringmessage);}

七、框架约定与最佳实践

7.1 命名约定

前缀含义示例
AddXXX注册服务AddHome(),AddProject()
UseXXX启用配置UseApplicationOptions()
TryAdd安全注册(不覆盖)TryAddSingleton()
IocXXXIOC 访问类IocLogService,IocMessage

7.2 最佳实践

1. 面向接口编程
// ✅ 推荐:依赖接口publicclassMyPresenter{privatereadonlyILogService_logService;publicMyPresenter(ILogServicelogService){_logService=logService;}}// ❌ 不推荐:依赖具体类publicclassMyPresenter{privatereadonlyLogService_logService=newLogService();}
2. 优先使用 TryAdd
// ✅ 推荐:不会覆盖用户自定义实现services.TryAdd(ServiceDescriptor.Singleton<IMyService,DefaultService>());// ❌ 不推荐:会覆盖已注册的服务services.AddSingleton<IMyService,DefaultService>();
3. 使用扩展方法封装
// ✅ 推荐:封装注册逻辑publicstaticIServiceCollectionAddMyFeature(thisIServiceCollectionservices){services.AddOptions();services.TryAddSingleton<IMyService,MyService>();services.TryAddSingleton<IMyPresenter,MyPresenter>();returnservices;}// 使用services.AddMyFeature();
4. 服务不存在时的优雅处理
// ✅ 推荐:安全获取varservice=Ioc.GetService<IMyOptionalService>(throwIfNone:false);if(service!=null){service.DoSomething();}// ✅ 推荐:检查服务是否存在if(Ioc.Exist<IMyOptionalService>()){Ioc.GetService<IMyOptionalService>().DoSomething();}

八、常见问题解答

Q1:为什么要使用 IOC?

A:IOC 带来以下好处:

  • 解耦:组件之间不直接依赖具体实现
  • 可测试:可以轻松替换为 mock 对象
  • 可维护:服务注册集中管理
  • 可扩展:新增服务只需注册即可

Q2:什么时候用 Singleton/Scoped/Transient?

A

  • Singleton:无状态的全局服务(日志、配置)
  • Scoped:需要在特定上下文内共享的服务(数据库连接)
  • Transient:轻量级、每次使用都需要新实例的服务

Q3:如何在 XAML 中使用服务?

A:使用IocExtension

<Windowxmlns:ioc="clr-namespace:H.Iocable"><ContentControlContent="{ioc:IocExtension Type={x:Type local:IMyPresenter}}"/></Window>

Q4:服务注册顺序重要吗?

A:对于Add方法,后注册的会覆盖先注册的;对于TryAdd方法,只有第一次注册有效。


九、总结

H.Iocable 是对 Microsoft.Extensions.DependencyInjection 的轻量级封装,核心价值在于:

  1. 简化使用:提供静态入口Ioc,无需注入即可获取服务
  2. XAML 支持:通过IocExtension实现 XAML 中的服务绑定
  3. 批量服务获取GetAssignableFromServices<T>支持获取多个实现
  4. 优雅的异常处理:服务不存在时提供清晰的错误信息

掌握 IOC 容器是理解 WPF-Control 框架的关键,它是连接所有模块、服务和控件的核心纽带。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/27 4:49:00

AI实时翻译实现BurpSuite中文界面(无需修改源码)

1. 这不是简单的“改个语言”&#xff0c;而是BurpSuite中文生态的破冰点你有没有在刚打开BurpSuite时&#xff0c;面对满屏英文菜单、弹窗提示和错误日志&#xff0c;下意识地去翻找Settings → User Interface → Language&#xff0c;却发现下拉框里只有English、Franais、D…

作者头像 李华
网站建设 2026/5/22 7:57:32

海外网红营销AI skills到底是什么?2026年出海品牌选型指南

这两年&#xff0c;海外网红营销圈冒出了一个新词——AI skills。很多人第一次听到时有点摸不着头脑&#xff1a;这不就是AI功能吗&#xff1f;换个名字而已&#xff1f;但其实&#xff0c;它和传统AI功能还真不是一回事。本文想做的事很简单&#xff1a;讲清楚这个新概念到底是…

作者头像 李华
网站建设 2026/5/22 7:54:02

Unity版本管理实战:下载、校验与团队协同工作流

1. 这不是“找链接”的事&#xff0c;而是Unity开发者绕不开的版本治理起点很多人第一次搜“unity各版本下载地址”&#xff0c;是被一个报错逼到墙角的&#xff1a;项目打开提示“Project was last opened with Unity 2021.3.15f1, but you are using 2022.3.20f1 — some fea…

作者头像 李华
网站建设 2026/5/22 7:52:35

【FlinkSQL笔记】(一)什么是Flink SQL

一、什么是 Flink SQL Flink SQL 是 Flink 官方推出的流批一体 SQL 计算语法&#xff0c;无需编写大量代码&#xff0c;用标准SQL即可开发实时计算任务。 核心优势&#xff1a; 零代码压力&#xff1a;复用标准SQL语法&#xff0c;学习成本极低 企业主流&#xff1a;云平台&a…

作者头像 李华
网站建设 2026/5/22 7:52:34

Unity Quest部署排障指南:从编译到稳定运行的全链路实践

1. 这不是“打包就能跑”的简单事&#xff1a;为什么Quest部署总卡在最后一公里很多人在Unity里做完VR场景&#xff0c;点下Build&#xff0c;看着进度条走到100%&#xff0c;心里一松——成了。结果把APK拖进Quest&#xff0c;头显一戴&#xff0c;黑屏、闪退、手柄失联、画面…

作者头像 李华
网站建设 2026/5/22 7:52:30

Unity VR控制器输入控制重构:从延迟优化到语义分层

1. 这不是“加个按钮”就能搞定的事&#xff1a;为什么高级VR控制器编程必须从输入控制重构开始在Unity里给VR控制器加个“按A键触发抓取”&#xff0c;三分钟就能跑通——但那只是Demo&#xff0c;不是产品。我带过三个VR项目&#xff0c;前两个都卡死在“明明逻辑写对了&…

作者头像 李华