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

Prism框架下IoC容器:告别手动依赖注入的噩梦

关于依赖注入的基础概念,我就不啰嗦太多了,咱们直接从咱们写代码时最头疼的痛点说起,这样大家更容易理解为什么我们需要 Prism 里的 IoC 容器。(注:本篇文章由AI结合个人文章优化而成,个人确认内容无误,如有错误处请联系指出,谢谢)

从新手的写法,到我们遇到的问题

刚学 C# 的时候,我写代码的思路特别直接:哪里要用服务,我就直接new一个实例出来不就行了?比如我要调用用户服务,就var service = new UserService();,要调用日志服务,就var logger = new FileLogger();

但是写着写着就发现不对了:比如我的客户端类(比如 WPF 里的 ViewModel,这是咱们的高层模块)要用到好几个服务,那我就要在 ViewModel 里把这些服务都new一遍。这时候问题就来了:如果我要换服务的实现怎么办?

比如原来的日志服务是写本地文件的,现在产品说要改成上报到服务器,那我就要把所有new FileLogger()的地方都改成new RemoteLogger()。如果这个日志服务在 10 个 ViewModel 里都用到了,那我就要改 10 次代码,改完还要一个个检查有没有漏改,万一漏了一个,线上出问题都看不到日志,这不纯纯给自己找事吗?

这就是典型的高层模块依赖低层模块—— 我改个低层的服务实现,高层的调用代码全都要跟着改,太麻烦了。

怎么解决?从 DIP 原则说起

那怎么解决这个问题?咱们换个思路:高层模块不要直接依赖具体的服务类,而是依赖一个抽象的接口。所有的服务都实现这个接口,高层模块只调用接口里的方法,根本不管你这个接口具体是哪个类实现的。

比如我定义个ILogger接口,里面有个Log方法,然后FileLoggerRemoteLogger都实现这个接口。那我的 ViewModel 里就只用到ILogger,不管你是本地还是远程,我只要调用_logger.Log()就行。

这样一来,不管我怎么改服务的实现,ViewModel 的代码一行都不用动!这就是咱们常说的DIP 依赖倒置原则:高层不依赖低层,两者都依赖抽象。

理清楚:DIP、DI、IoC、容器,到底都是啥?

说到这,很多人会搞混这几个词,我给大家用大白话理清楚,别再搞混了:

  • DIP:是咱们的指导思想,就是告诉我们 “要依赖抽象,不要依赖具体”,这是原则,是我们要达到的目标。

  • DI 依赖注入:是实现 DIP 的具体手段,就是把依赖的对象,从外面传给需要它的类,而不是类自己new。比如 ViewModel 需要 ILogger,我就把 logger 通过构造函数传进去,这就是最常用的构造注入,除此之外 C# 原生还支持属性注入和接口注入,不过日常开发用构造注入就够了。

  • IoC 控制反转:是我们用了 DI 之后得到的结果。原来对象的创建是由我们自己控制的,我要什么就自己new,现在反过来,把创建对象的控制权交给了别人,这就是 “控制反转”。

  • IoC 容器:就是帮我们自动做 DI 的工具!如果没有容器,我们就要手动把所有依赖都创建好,然后一个个传进去,有了容器,这些活它都帮我们干了。

如果你只是想了解 IoC 模式的基础理论,推荐你看看这篇入门文章:https://www.cnblogs.com/liuhaorain/p/3747470.html,讲得通俗易懂。今天这篇我们主要讲在 WPF 里用 Prism 的 IoC 容器,能给我们带来什么实际的好处。

手动依赖注入,到底有多麻烦?

咱们先看看,不用容器的话,手动做 DI 是个什么样子。

先看个简单的例子,假设我们有这么几个服务:

// 定义接口和实现类
public interface IA { }
public class A : IA { public A() { } }public interface IB { }
public class B : IB
{private IA _a;public B(IA a) { _a = a; }
}public interface IC { }
public class C : IC
{private IA _a;private IB _b;// C同时依赖A和Bpublic C(IA a, IB b) { _a = a; _b = b; }
}public class Client
{private IC _c;public Client(IC c) { _c = c; }public void DoSomething() { /* 使用_c */ }
}

你看,依赖关系其实已经有点绕了,那我们要使用这个 Client,手动管理依赖的话,要这么写:

// 必须严格按顺序创建,少一个都不行
IA a = new A();
IB b = new B(a);
IC c = new C(a, b);
Client client = new Client(c);
client.DoSomething();

这才 3 个依赖,你已经能感觉到有点麻烦了对吧?那如果依赖链更长呢?

咱们来个更贴近实际的长依赖链的例子:

// 模拟一个更长的依赖链:A ← B ← C ← D ← E ← F ← CanService
public interface IA { }
public class A : IA { public A() { } }public interface IB { }
public class B : IB
{private IA _a;public B(IA a) { _a = a; }
}public interface IC { }
public class C : IC
{private IA _a;private IB _b;public C(IA a, IB b) { _a = a; _b = b; }
}public interface ID { }
public class D : ID
{private IC _c;public D(IC c) { _c = c; }
}public interface IE { }
public class E : IE
{private ID _d;public E(ID d) { _d = d; }
}public interface IF { }
public class F : IF
{private IE _e;public F(IE e) { _e = e; }
}// 我们真正要用的业务服务
public interface ICanService
{void DoWork();
}public class CanService : ICanService
{private IF _f;public CanService(IF f) { _f = f; }public void DoWork() { Console.WriteLine("完成了服务"); }
}

这时候,如果我们还是手动管理依赖,要怎么用这个CanService

// 手动创建所有依赖,顺序不能错,一个都不能漏
IA a = new A();
IB b = new B(a);
IC c = new C(a, b);
ID d = new D(c);
IE e = new E(d);
IF f = new F(e);
ICanService service = new CanService(f);// 最后才能把service传给ViewModel
MainViewModel vm = new MainViewModel(service);

你看看这代码,是不是看着就头大?这才 6 个依赖,要是以后再加个 G、H,那这行代码是不是要写得更长?

而且如果哪天B的依赖变了,比如B现在需要一个IE的依赖了,那你整个创建顺序都要改,所有用到B的地方都要改代码,稍不注意就出错。

这还只是一个服务,如果我们有十几个这样的服务,那手动管理依赖简直是噩梦。

Prism 的 IoC 容器:帮你搞定所有麻烦

这时候 Prism 的 IoC 容器就该出场了!有了它,上面那一大串手动创建的代码,我们全都不用写了!

怎么用?其实特别简单,就两步:

第一步:把服务注册到容器里

这个注册一般放在App.xaml.cs里,因为这是 WPF 程序的入口,注册完整个程序都能用。

首先我们的 App 类要继承PrismApplication,这是 Prism 给我们的基础类,然后重写RegisterTypes方法,在这里把所有的服务都注册进去:

public partial class App : PrismApplication
{protected override void RegisterTypes(IContainerRegistry containerRegistry){// 告诉容器:当别人要IA的时候,你给我一个A的实例,而且整个程序只有一个(单例)containerRegistry.RegisterSingleton<IA, A>();containerRegistry.RegisterSingleton<IB, B>();containerRegistry.RegisterSingleton<IC, C>();containerRegistry.RegisterSingleton<ID, D>();containerRegistry.RegisterSingleton<IE, E>();containerRegistry.RegisterSingleton<IF, F>();containerRegistry.RegisterSingleton<ICanService, CanService>();}
}

这里的RegisterSingleton就是说,这个服务整个程序里只有一个实例,不管谁来拿,都是同一个,这样可以节省资源,也方便共享状态。如果你需要每次用的时候都创建一个新的,也可以用RegisterTransient,这个看你的需求。

你看,我们就注册了这么几行,剩下的事容器都帮我们干了。它会自动帮我们分析每个服务的依赖,谁需要谁,它都门清,不用我们管。

很多人会问,Prism 底层用的是什么容器?老版本用 Unity,新版本默认用 DryIoc,不过对我们来说完全不用管!我们用的是 Prism 给我们的统一的IContainerRegistry接口,不管底层用什么容器,我们的注册代码都是一样的,不用改。

第二步:用的时候,直接声明你需要什么

注册完了,我们用的时候就更简单了!比如我们在 ViewModel 里要用CanService,只要在构造函数里声明你需要什么就行了:

public class MainViewModel
{private ICanService _server;// 你只要说:我需要一个ICanServicepublic MainViewModel(ICanService server){_server = server;// 直接用就行!容器已经帮你把所有A、B、C、D、E、F都创建好了_server.DoWork();}
}

就这么简单?对!就这么简单!

你再也不用管那些乱七八糟的依赖顺序了,只要告诉容器我要什么,容器就会把所有东西都准备好给我送过来。就像你去餐厅吃饭,你只要告诉服务员你要吃什么菜,不用自己去菜市场买买菜、洗洗菜、炒菜,厨师都帮你做好了端上来,你直接吃就行。

一个更贴近日常的例子:日志服务

说到这,可能还有人没太有感觉,咱们再举个咱们日常开发天天都会碰到的例子,大家就秒懂了:

我们有个日志服务,原来我们是写本地文件的,那如果不用容器的话,我们的代码里到处都是var logger = new FileLogger();

后来产品说,我们要把错误日志上报到服务器,方便线上排查问题,那我们就要把所有的new FileLogger()都改成new RemoteLogger(),如果有 20 个 ViewModel 用到了日志,那就要改 20 次,改完还要一个个检查有没有漏,太麻烦了。

但是用了容器之后呢?

  1. 我们定义个ILogger接口,然后FileLoggerRemoteLogger都实现这个接口

  2. 注册的时候,我们这么写:

// 原来的本地日志
containerRegistry.RegisterSingleton<ILogger, FileLogger>();
  1. 所有的 ViewModel 里,只要这么写:
public class UserViewModel(ILogger logger)
{private readonly ILogger _logger = logger;// 业务代码里直接用_logger.Log()就行,不管它是本地还是远程
}

那当我们要改成远程日志的时候,只要改注册的那一行:

// 就改这一行!所有地方的日志都变成远程的了
containerRegistry.RegisterSingleton<ILogger, RemoteLogger>();

就这一行!所有 20 个 ViewModel 的日志自动就都变成远程的了,不用改任何一行业务代码,也不用担心漏改,是不是爽翻了?

用了 IoC 容器,我们到底得到了什么?

总结一下,IoC 容器给我们带来的好处,真的太多了:

  1. 不用再手动管依赖了:再也不用纠结先 new A 还是先 new B,容器自动帮你搞定所有依赖的创建顺序,再也不会因为依赖顺序错了而出错。

  2. 改服务实现太简单了:只要改注册的那一行,所有用到的地方自动生效,不用改业务代码,也不用担心漏改。

  3. 代码更干净:ViewModel 里只要声明我需要什么服务,不用管这个服务怎么来的,你专注写你的业务逻辑就行了,其他的交给容器。

  4. 方便管理对象生命周期:是要整个程序共用一个实例,还是每次用都新创建一个,注册的时候一句话就搞定,不用自己管。

  5. 解耦!解耦!解耦!:重要的事说三遍,高层和低层完全解耦,低层怎么改,高层都不用动,代码的可维护性直接拉满。

新手容易踩的两个坑

最后给刚接触的朋友提两个小提醒,别踩坑:

  1. 优先用构造函数注入:这是 Prism 最推荐的方式,最稳定,也最清晰,别人一看你的构造函数就知道你这个类依赖了什么服务,一目了然。

  2. 不要手动去 Resolve 服务:很多新手刚学的时候,会忍不住去容器里自己拿服务,比如Container.Resolve<ILogger>(),这其实就是服务定位器模式,会破坏依赖注入的解耦,尽量不要这么做,老老实实通过构造函数声明依赖就好。

最后

其实 IoC 容器说复杂也复杂,说简单也简单,核心就是一句话:不要自己 new,告诉容器你需要什么,容器给你准备好

用熟了之后,你会发现写代码的效率提升了太多,再也不用跟那些乱七八糟的依赖较劲了。

如果大家想深入了解 IoC 和 DI 的原理,可以看看这篇深入的文章:https://www.cnblogs.com/fuchongjundream/p/3873073.html

希望这篇文章能帮到刚接触 Prism 的朋友,也欢迎大家一起交流讨论~

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

相关文章:

  • 抖音视频保存本地怎么去水印?2026 最新去水印方法实测,这几种方式全搞定
  • 2026 发新闻稿平台推荐:AI 时代主流发稿渠道深度测评与选型指南 - 博客湾
  • 2026全平台海外仓一件代发权威榜单|优选头部服务商深度评测 - 资讯焦点
  • 山东一卡通回收攻略:如何选择靠谱的线上回收平台? - 团团收购物卡回收
  • 如何免费解锁WeMod高级功能:简单三步轻松获得完整游戏修改体验
  • 鸣潮游戏自动化终极指南:5分钟配置解放双手的智能助手
  • 2026成都防水漏水维修公司靠谱品牌排名:雨和虹防水维修/雨盛防水维修/秦鑫斌防水维修/森之澜漏水检测/能亿防水补漏/成诺防水修缮 - 雨和虹防水维修
  • 2026年全屋定制TOP5:东莞三喜家具有限公司,高性价比之选! - 速递信息
  • 模块三-数据清洗与预处理——13. 缺失值处理(下):填充缺失值
  • 北京CBD写字楼租赁:服务世界500强与国央企的专业中介机构分析 - 资讯焦点
  • 终极指南:如何快速掌握Kubernetes容器编排与Java应用部署
  • 从埋点混乱到全链路可观测,DeepSeek团队重构Jaeger体系的7个关键决策,第4个90%团队都做错了
  • 暗黑破坏神2存档编辑器终极指南:5分钟掌握免费存档修改技巧
  • 毕业设计:基于Springboot+Vue在线鲜花销售管理系统(源码)
  • 2026京东优惠券国补补贴怎么领?京东618买苹果手机电脑、空调家电优惠券哪里领?京东红包+国补补贴+满减叠加技巧 - 资讯焦点
  • Paylinks高级功能指南:组合支付、分账与异常退款处理的终极教程
  • ARM调试架构与多核调试实战解析
  • 2026年东莞全屋定制:东莞三喜家具有限公司,深耕多年口碑优选服务商 - 速递信息
  • 2026年5月版权音乐平台实测排行:5大平台深度对比,商用选曲不踩坑全指南 - 拾光而行
  • 2026北京GEO优化公司实力解析盘点【附选型避坑指南】 - 资讯焦点
  • 如何选择正规京东 e 卡回收平台 - 购物卡回收找京尔回收
  • Trilinos框架:科学计算中的多物理场耦合与异构计算实践
  • Beatpilot:基于编码活动实时生成个性化BGM的AI音乐引擎
  • 恒温恒湿试验箱测评:海孚威两款主力机型深度对比 - 资讯焦点
  • LeetCode 键值映射题解
  • 2026深圳防水漏水维修公司靠谱品牌排名:雨和虹防水维修/雨盛防水维修/秦鑫斌防水维修/森之澜漏水检测/能亿防水补漏/成诺防水修缮 - 雨和虹防水维修
  • 告别延时函数!三种驱动WS2812方案对比(SPI/DMA/PWM)及STM32选型建议
  • 解锁Trigger.dev高级特性:Webhooks与延迟任务实战指南 [特殊字符]
  • 保护心脑血管健康哪个品牌鱼油好?深海鱼油十大品牌:血管养护选对才有效 - 资讯焦点
  • 小学生用脑补什么牌子营养品?2026权威补脑营养品排行榜推荐:提升记忆力 - 资讯焦点