C#依赖注入

news/2025/9/20 14:37:24/文章来源:https://www.cnblogs.com/haoweiwei/p/19100718

一、什么是依赖注入?

依赖指的是:当一个类(A)需要另一个类(B)的功能才能完成工作时,A 就 "依赖" 于 B。
例如:OrderService需要用Logger记录日志,那么OrderService依赖于Logger

依赖注入的核心思想是:将依赖的创建和管理交给外部容器,而不是在类内部自行创建。
简单说就是 "谁需要依赖,就由外部把依赖传给它",而非自己创建。
 

二、为什么需要依赖注入?

先看一个 "没有依赖注入" 的反例:
// 日志类
public class Logger
{public void Log(string message) => Console.WriteLine($"日志:{message}");
}// 订单服务(依赖Logger)
public class OrderService
{// 自己创建依赖(问题所在)private readonly Logger _logger = new Logger();public void CreateOrder(){_logger.Log("订单创建成功");// 其他业务逻辑...
    }
}
View Code

这个例子中OrderService直接依赖Logger的具体实现,如果未来要替换为FileLogger,必须修改OrderService,这就违反了开闭原则,对扩展开放,对修改关闭

如果使用依赖注入,应该要生命一个抽象接口,让服务类去依赖接口,以后要添加别的类型的日志的适合直接添加就行,然后在逻辑代码中实例化接口,传入给服务类的构造函数。

// 抽象接口(依赖抽象)
public interface ILogger
{void Log(string message);
}// 具体实现
public class ConsoleLogger : ILogger
{public void Log(string message) => Console.WriteLine($"日志:{message}");
}// 订单服务(依赖通过外部注入)
public class OrderService
{private readonly ILogger _logger;// 构造函数注入(推荐方式)public OrderService(ILogger logger){_logger = logger; // 依赖由外部传入,而非自己创建
    }public void CreateOrder(){_logger.Log("订单创建成功");}
}
View Code

三、依赖注入的 3 种方式

在 C# 中,依赖注入通常通过以下 3 种方式实现:

1. 构造函数注入(最推荐)

通过类的构造函数接收依赖,是.NET 中最常用的方式。适用于 "必须的依赖"(类没有它就无法工作)。
 
public class UserService
{private readonly ILogger _logger;// 构造函数注入:创建UserService时必须提供ILoggerpublic UserService(ILogger logger){_logger = logger ?? throw new ArgumentNullException(nameof(logger));}
}
View Code

2. 属性注入

通过公共属性接收依赖,适用于 "可选的依赖"(类没有它也能工作)。
在ASP.NET Core 中,通常配合[Inject]特性使用(如在视图中)
public class UserService
{// 属性注入:依赖是可选的public ILogger Logger { get; set; }public void DoWork(){Logger?.Log("执行工作"); // 注意判空
    }
}
View Code

3. 方法注入

通过方法参数接收依赖,适用于仅在特定方法中需要的依赖。
public class UserService
{public void DoWork(ILogger logger){logger.Log("执行工作"); // 仅该方法需要依赖
    }
}
View Code

 

四、微软自带的Microsoft.Extensions.DependencyInjection

1.核心原理是通过服务容器管理服务的注册、生命周期和依赖解析,其设计遵循 “依赖倒置” 和 “控制反转” 原则,核心流程可分为服务注册、容器构建和服务解

using Microsoft.Extensions.DependencyInjection;// 定义服务接口和实现
public interface IUserService { }
public class UserService : IUserService { }// 注册服务其实就是一个LIst<ServiceDescriptor>集合
var services = new ServiceCollection();
// 瞬时生命周期,new一个新的元素,参数:抽象类型为接口,具体实现类型,Transient是生命周期
services.AddTransient<IUserService, UserService>();
// 作用域生命周期
services.AddScoped<IUserService, UserService>();
// 单例生命周期
services.AddSingleton<IUserService, UserService>();

// 3. 构建服务提供器(DI容器)
_serviceProvider = services.BuildServiceProvider();

2.容器构建(BuildServiceProvider

当服务注册完成后,调用 services.BuildServiceProvider() 会创建 IServiceProvider 实例(默认实现为 ServiceProvider)。这一步是 “从注册到可用容器” 的关键转换,核心工作包括:

验证服务注册的合法性

为每个服务创建 “解析器”

初始化根容器与作用域管理

3.服务解析(GetService/GetRequiredService

当调用 serviceProvider.GetRequiredService<T>() 时,ServiceProvider 会按以下流程解析服务实例:

查找服务元数据

根据生命周期创建实例

 自动注入依赖(构造函数注入)

 处理 IDisposable 服务

4.最后总结原理就类似 :

1)注册阶段:new一个字典集合,注册:相当于添加元素,添加接口和实现类指定生命周期方式

2)构建阶段:BuildServiceProvider() 验证注册合法性,生成解析器,初始化根容器。

3)解析阶段:

据服务类型查找 ServiceDescriptor。按生命周期(Transient/Scoped/Singleton)创建实例,递归注入依赖。缓存实例(Scoped 缓存于作用域,Singleton 缓存于根容器)。

4)清理阶段:作用域或根容器释放时,自动清理 IDisposable 服务。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/908377.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

springboot手写源码总结

springboot手写源码总结先创建spring容器,然后将配置类(也就是启动类,因为上面有标注@ComponentScan和@Configuration注解)注册到spring容器中去,进行扫描。然后将spring容器绑定到servlet中,将servlet添加到tom…

完整教程:Docker Compose 一键启动多容器服务

完整教程:Docker Compose 一键启动多容器服务pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", &q…

【截稿倒计时、高录用、稳检索】2025年教育创新与信息技术国际学术会议(EIIT 2025)

【高录用、稳检索】 2025年教育创新与信息技术国际学术会议 2025 International Conference on Educational Innovation and Information Technology (EIIT 2025) 教育、创新、信息科学相关主题方向均可投稿 *参会者现…

低代码 + AI 构建智慧校园系统:某高校宿舍报修平台的48小时构建全流程

低代码 + AI 构建智慧校园系统:某高校宿舍报修平台的48小时构建全流程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: &…

[MCP][07]logging和progress等功能说明

除了基础的Prompt、Resource和Tool概念,FastMCP还提供了以下功能:Sampling、Elicitation、Logging、Progress、Proxy、Middleware、Composition和Authentication功能前言 截至目前(2025年9月19日),除了基础的Prom…

端口命令

1、查询指定端口(如查询 8080 端口):netstat -ano | findstr "8080"结果说明:LISTENING 表示端口正在监听;最后一列数字是 进程 PID2、通过 PID 查对应进程(如 PID 为 1234):tasklist | findstr &qu…

Microsoft OLE漏洞致远程代码执行安全公告解析

微软发布安全公告3010060,披露Windows OLE组件远程代码执行漏洞。攻击者通过特制PowerPoint文件实施定向攻击,本文提供受影响环境、攻击向量及临时缓解方案(Fix it工具、UAC配置、EMET部署)的详细技术指导。セキュ…

写代码还是写提示词?——Prompt 工程是不是程序员的新技能树

过去二十年,程序员的核心技能几乎没变:学语言、写代码、调 bug。但是 AI 大模型的出现,正在悄悄改写这套逻辑。 有人开始疑惑:👉 “未来的程序员,还需要会写代码吗?”👉 “Prompt 工程(提示词工程)是不是新…

c-store发送dcm文件超时

错误代码:FellowOakDicom.Network.DicomAssociationRequestTimedOutException:“The association request timed out 3 times, waiting 5000ms each time for the association response等超时返回解决方案:首先要明白…

解码C语言模块化编程

一、模块化设计原则原则 说明 示例高内聚 模块内部功能紧密相关 将数学计算函数集中到 math_utils 模块低耦合 模块间依赖最小化(通过接口通信) 使用头文件声明接口,隐藏实现细节单一职责 每个模块只解决一个特定问…

redis存储漂流瓶信息

问题 比如漂流瓶的数据,都放在redis里,支持12小时过期,支持最大捡起数,支持重复捡取。 漂流了内容,有头像,昵称,内容(文字,语音),年龄,城市,过期时间,读取次数。 支持随机捡瓶子。 使用tp5需要怎么设计…

hashcat高效爆破Wi-Fi密码方法(比aircrack-ng快)

​ (tip:本文所有操作在个人测试环境下运行,请不要用于违法行为) 准备工具: 电脑 kali-linux-2025.2系统(4G内存以上) ----在aircrack爆破时,无线网络审计套件(aircrack-ng)作为内置模组组件常用于一体化流程爆破密…

更新到macOS Sequoia后,chrome无法用ip访问192.168.*

设置 -> 隐私与安全 -> 本地网络 开启chrome按钮 System Settings -> Privacy & Security -> Local Network

Typora标题自动显示序号,大纲中也显示序号

Typora标题自动显示序号,大纲中也显示序号 代码一:一级标题显示序号点击查看代码 /*************************************** Header Counters in TOC (目录中的标题计数器)**************************************/…

【IEEE出版、格林威治大学主办】第六届信息科学与并行、分布式处理国际学术会议(ISPDS 2025)

第六届信息科学与并行、分布式处理国际学术会议(ISPDS 2025) 2025 6th International Conference on Information Science, Parallel and Distributed Systems 在这里看会议官网详情 2025年10月31日-11月2日 | 英国-…

​​Snipaste 2.10.1.dmg截图安装教程|Mac电脑拖拽安装详细步骤​

​​Snipaste 2.10.1.dmg截图安装教程|Mac电脑拖拽安装详细步骤​​​Snipaste​​ 是一款超好用的 ​​截图 + 贴图工具​​,特别适合 Mac 用户日常使用。 第一步:下载文件 (这一步你可能已经完成了) 安装包下载…

Day18面向对象的基本认识与回顾方法的定义

package oop;import java.io.IOException;//类:Demo1 public class Demo1 {//main:方法public static void main(String[] args) {}/*修饰符 返回值类型 方法名(.....){方法体return 返回值}*///return 结束方法…

【2025-09-19】连岳摘抄

23:59大家知道,时间有时像鸟儿一样疾飞,有时像蠕虫一样爬行,不过,当一个人觉察不到时间的快慢时,他往往会感到特别幸福。——屠格涅夫本科学历有没有用呢?或者说,教育有没有用呢?一、当然是有用的。因为我们这…

【2025-09-18】工作情绪

20:00人间是值得赞美的,生活应加以珍惜。——启功昨晚临下班前,又下了一场大雨。然而我还是没有开车上班。最后我还是投不了球,也做不了其它运动,就这样坐在办公室等何太下班来接我。一等,又是两个小时。加上白天…

Ubuntu 系统部署 LNMP 环境详细教程(附shell脚本一键部署↓) - 指南

Ubuntu 系统部署 LNMP 环境详细教程(附shell脚本一键部署↓) - 指南pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: &q…