当前位置: 首页 > news >正文

DI容器与导航系统:从注册到取用的完整链路

DI容器与导航系统:从注册到取用的完整链路

一次学习对话的技术沉淀,讲清依赖注入容器的底层机制,以及桌面应用中导航系统的实现原理。


一、从一个注册方法说起

在做桌面应用开发时,我们经常看到类似这样的代码:

services.AddSingletonNavigation<LoginView, LoginViewModel>();

一行代码看似简单,背后暗藏了三个动作。要理解这行代码,得先弄明白什么叫"依赖注入容器"。

什么是依赖注入(DI)容器?

依赖注入容器本质上是一张"登记表"。它记录了"如果你需要某某类型的对象,我应该给你什么"。

打个比方:你去酒店前台,“我需要一间房”——前台查登记表,“502号房,这是钥匙”。你不用自己盖房子,不用管房间怎么布置,只需要说你需要什么类型,前台给你就行。

在代码世界里,IServiceCollection就是这张登记表。它是一个接口类型,代表"服务注册表"——所有需要被管理的对象类型,都要先往这张表里登记。

三种生命周期

往表里登记时,除了记录类型,还要指定生命周期——这个对象能活多久、每次给同一个还是不同的实例:

生命周期含义类比
Singleton(单例)整个应用生命周期内,永远返回同一个实例酒店的公共WiFi密码,所有人共享同一个
Transient(瞬态)每次请求都创建一个全新实例一次性纸杯,每次给你一个新的
Scoped(作用域)在同一个"范围"内返回同一实例,跨范围重新创建一场宴会内共用一套餐具,下一场换新的

AddSingletonNavigation用的是 Singleton——登录页面和登录逻辑在整个应用运行期间只需要一份。


二、注册的背后:一条语句三条记录

回到开头那句代码:

services.AddSingletonNavigation<LoginView, LoginViewModel>();

这一句实际往登记表里塞了三条记录

登记的类型钥匙(Key)生命周期
LoginViewSingleton
LoginViewModelSingleton
object"LoginView"KeyedSingleton

为什么需要第三条?

因为导航系统在工作时,只知道一个字符串"LoginView"——它不知道具体是什么类型。所以需要用"钥匙"的方式来存取:登记时多记一列 Key,取的时候用 Key 来找。

这就是Keyed Service(键控服务)的概念——同一个类型可能注册多份,用不同的 Key 区分。比如:

"LoginView" → LoginView 实例 "SettingsView" → SettingsView 实例

两者都是object类型,但 Key 不同,取出来的对象不同。

概念延伸:其他 DI 容器

微软的IServiceCollection/IServiceProvider是 .NET 内置的轻量级 DI 实现。业界还有更强大的第三方容器:

  • Autofac:最流行,支持属性注入、模块化配置、AOP 拦截
  • DryIoc:轻量快速,启动时间短,适合性能敏感场景
  • Unity / Ninject:曾经的流行之选,目前已停止维护(技术上称为"已凉")

选择 DI 容器的核心考量:功能丰富度 vs 启动性能 vs 社区活跃度。


三、取用:从登记表拿实例

两种取法

从 DI 容器取实例有两种方式:

方式一:按类型取

var vm = provider.GetService<LoginViewModel>();

直接说"给我一个 LoginViewModel",容器查表,找到就返回。

方式二:按键取

var view = provider.GetRequiredKeyedService<object>("LoginView");

用 Key 来找。导航系统用的就是这种方式——因为导航只知道字符串,不知道具体类型。

两者的关键区别:

  • GetService<T>():找不到返回 null,不会抛异常
  • GetRequiredKeyedService<T>(key):找不到直接抛异常("Required"意味着必须存在)

IServiceCollection vs IServiceProvider

这两个接口常被混淆,其实职责非常清晰:

IServiceCollection = 登记处(只负责记录) IServiceProvider = 执行端(真正 new 出对象给你)

好比民政局:

  • IServiceCollection是档案室——记录了谁跟谁是夫妻
  • IServiceProvider是办事窗口——你递材料,它真的给你办证

Build Service Provider 的过程,就是把登记表"编译"成可执行的查询引擎。此后所有的GetService调用,都由 Provider 高效响应。


四、导航系统的实现原理

先导知识:MVVM 与 ContentControl

MVVM(Model-View-ViewModel)是桌面应用开发的主流模式:

  • Model:数据,不管界面长什么样
  • View:界面,只管展示,不管逻辑
  • ViewModel:中间人,连接 Model 和 View,管逻辑

ContentControl是 WPF 中的一个容器控件——它有一个Content属性,放什么它就显示什么。导航的本质,就是切换 ContentControl 的 Content。

导航三步走

第一步:XAML 贴标签

在界面上找一个 ContentControl,给它贴一个"区域名"标签:

ContentControl NavigationAttach.RegionName="mainContent"

这告诉系统:“这个容器叫 mainContent,以后可以通过这个名字找到它”。

这里用到了附加属性(Attached Property)——NavigationAttach.RegionName不是 ContentControl 自己的属性,而是别人"附加"给它的。类似于你在快递盒上贴便利贴,盒子本身没变,但多了一条信息。

第二步:代码触发导航

navService.RequestNavigate("mainContent", "LoginView");

两个参数:

  • "mainContent"→ 往哪个容器里放(定位目标容器)
  • "LoginView"→ 放什么页面(从 DI 容器按键取出)

第三步:导航服务内部流程

ShareNavigationService内部做了这几件事:

  1. 找到容器:遍历视觉树,找到RegionName="mainContent"的 ContentControl
  2. 取出页面GetRequiredKeyedService<object>("LoginView")从 DI 拿新页面
  3. 去重判断:如果新页面和当前页面是同类型,只刷新参数,不重建实例(避免不必要的开销)
  4. 通知退场:调用旧页面的NavigateFrom(),告诉它"你要被换掉了"
  5. 替换内容control.Content = newView
  6. 通知上场:调用新页面的NavigateTo(),告诉它"轮到你显示了"

IShareNavigationAware 接口

这是一个导航生命周期接口,定义了两个方法:

void NavigateTo(NavigationContext context); // 进场时调用 void NavigateFrom(NavigationContext context); // 退场时调用

ViewModel 实现这个接口后,导航服务会自动调用这两个方法。ViewModel 不需要知道是谁触发的导航,只需要响应进场/退场事件。

这体现了依赖倒置原则(DIP)——高层模块(导航服务)不依赖低层模块(具体页面),两者都依赖抽象(接口)。

is 关键字:类型检测一步到位

if (newView.DataContext is IShareNavigationAware awareVM) { awareVM.NavigateTo(context); }

is关键字同时完成了两件事:

  1. 类型检测:判断 DataContext 是否实现了 IShareNavigationAware 接口
  2. 类型转换:如果实现了,直接赋值给变量awareVM

这叫做模式匹配(Pattern Matching),C# 7.0 引入。比传统的as+ null 检查更简洁、更安全。


五、完整闭环

把整个流程串起来:

【注册阶段】 AddSingletonNavigation<LoginView, LoginViewModel>() → DI 登记表多了三条记录 【构建阶段】 BuildServiceProvider() → 登记表编译成可查询引擎 【取用阶段】 GetRequiredKeyedService<object>("LoginView") → 工厂方法创建 LoginView → 自动绑定 LoginViewModel 到 DataContext 【导航阶段】 RequestNavigate("mainContent", "LoginView") → 找到 "mainContent" ContentControl → Content = LoginView → UI 显示登录页面

六、总结与思考

核心概念回顾

概念一句话解释
DI 容器一张记录"谁对应什么实例"的登记表
Singleton全局唯一实例,从生到死都是它
Transient每次给你新的,不重复使用
Keyed Service同类型多实例时,用 Key 区分
导航切换 ContentControl.Content 的过程
进场/退场页面切换时的生命周期通知

为什么这套设计好?

  1. 解耦:View 和 ViewModel 通过 DI 容器间接关联,互不知道对方的具体实现
  2. 可测试:ViewModel 不依赖具体 View,可以单独做单元测试
  3. 可扩展:新增页面只需注册一对 View/ViewModel,导航系统自动支持
  4. 关注点分离:界面、逻辑、数据各管各的,修改一方不影响其他

学习方法论:一次说清一种概念

学习复杂系统的最佳策略是分治法:把大问题拆成小问题,一次只攻克一个概念。就像本文的组织方式——先讲 DI 容器是什么,再讲怎么注册,再讲怎么取用,最后讲如何串联成导航系统。每个概念独立成块,块与块之间有清晰的衔接。

这也符合认知负荷理论:人一次能处理的新信息有限,过载了就会"学不进去"。把概念拆碎、讲透,才是高效学习的正道。


本文基于桌面应用开发中的实际学习记录整理,聚焦 DI 容器与导航系统的核心原理。

http://www.jsqmd.com/news/1319190/

相关文章:

  • Qiankun微前端实战:从白屏到通信的完整避坑指南
  • Ubuntu 22.04部署OpenStack私有云实战指南
  • 2026 中牟装修公司综合实力排名,首选 TOP1 富豫装饰 - 滚动商讯
  • 5分钟学会智慧树自动刷课:终极Chrome插件安装与使用指南
  • 5分钟终极指南:如何在现代电脑上完美运行《塞尔达传说:时之笛》
  • 全息MIMO信道建模与Matlab仿真实践
  • three.js 编辑器的扩展点设计
  • 140、YOLOv8改进实战:Label Smoothing标签平滑在分类分支中的实现与过拟合抑制
  • 5分钟永久保存你的QQ空间青春记忆:GetQzonehistory一键备份终极指南
  • LAV Filters:强力解码神器,让你的Windows媒体播放再无障碍 ✨
  • 山东优质抽沙船厂家推荐从设备选型到服务覆盖的全方位指南 - 栈上春秋
  • 每日极客日报 · 2026年08月03日
  • 东台藏匠心老店,轟車軒守护行车微光 - Ayu8888
  • cas:2055048-42-3 ,Dde Biotin-PEG4-Picolyl azide,DDE-生物素-四聚乙二醇-吡啶甲基叠氮,DDE-生物素-PEG4-吡啶甲基叠氮
  • 010、YOLOv12推理部署框架ONNX/TensorRT/NCNN适配指南:从PyTorch到移动端的完整流程
  • HTTPS加密原理与实战:从握手到安全优化
  • 教培系统Excel批量导入功能的设计方案与数据校验机制
  • 百度网盘下载只有几KB?2026最新百度网盘提速/加速保姆级避坑教程
  • 桥梁防撞护栏厂家推荐安全防护与景观协调的解决方案 - 栈上春秋
  • UE5子关卡拆分:解决美术程序协作冲突,优化场景资产管理
  • Matlab实现配电网光伏储能双层优化配置模型
  • 从零部署FileBrowser:打造私有云盘与Linux文件管理解决方案
  • 优质清淤船厂家推荐助力水域治理高效进行 - 栈上春秋
  • C++ Lambda捕获机制深度解析:从悬垂引用到安全编程实践
  • Algorithmic-Art技能解析:代码生成艺术的核心技术与实践
  • 常州滚针轴承采购怎么选?2026 靠谱厂家排名与选型指南 - 资讯综合
  • AI编程实战:零代码经验开发Mac音频管理工具并实现盈利
  • 2026 年武汉 CNC 机床维修、机床改造怎么做?工厂实测心得 - LYL仔仔
  • 所调用的大模型也会改变
  • JS基础学习03