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.cs | XAML 中通过标记扩展获取服务 | XAML 绑定服务 |
IocBindable.cs | 可绑定的 IOC 对象,实现 INotifyPropertyChanged | MVVM 绑定场景 |
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.cs的ConfigureServices方法中注册:
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() |
IocXXX | IOC 访问类 | 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 的轻量级封装,核心价值在于:
- 简化使用:提供静态入口
Ioc,无需注入即可获取服务 - XAML 支持:通过
IocExtension实现 XAML 中的服务绑定 - 批量服务获取:
GetAssignableFromServices<T>支持获取多个实现 - 优雅的异常处理:服务不存在时提供清晰的错误信息
掌握 IOC 容器是理解 WPF-Control 框架的关键,它是连接所有模块、服务和控件的核心纽带。
