C# 中,依赖注入(DI)的实现方式 - 教程

news/2025/11/15 15:54:38/文章来源:https://www.cnblogs.com/yangykaifa/p/19225650

前面介绍过 什么是控制反转(IoC)?什么是依赖注入(DI)?以及实现原理

在 C# 中,依赖注入(DI)的实现方式主要分为手动注入通过 IoC 容器注入(如 .NET 自带的 Microsoft.Extensions.DependencyInjection)。以下是具体代码示例,涵盖常用场景和最佳实践。

一、手动依赖注入(基础示例)

手动注入不依赖第三方容器,直接通过代码传递依赖,适合简单场景,核心是构造函数注入(最推荐的方式)。

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

csharp

using System;
// 1. 定义抽象依赖(接口)
public interface IMessageSender
{void Send(string message);
}
// 2. 实现具体依赖(邮件发送)
public class EmailSender : IMessageSender
{public void Send(string message){Console.WriteLine($"[邮件发送] {message}");}
}
// 3. 实现具体依赖(短信发送)
public class SmsSender : IMessageSender
{public void Send(string message){Console.WriteLine($"[短信发送] {message}");}
}
// 4. 依赖方(通知服务):通过构造函数接收依赖
public class NotificationService
{private readonly IMessageSender _messageSender;// 构造函数注入:依赖由外部传入,且用 readonly 确保不可变public NotificationService(IMessageSender messageSender){_messageSender = messageSender ?? throw new ArgumentNullException(nameof(messageSender), "依赖不能为空"); // 校验依赖,避免空引用}public void NotifyUser(string username){_messageSender.Send($"用户 {username} 已收到通知");}
}
// 5. 调用:手动注入依赖
class Program
{static void Main(){// 手动创建依赖实例IMessageSender emailSender = new EmailSender();// IMessageSender smsSender = new SmsSender(); // 可切换为短信发送// 注入到依赖方NotificationService notificationService = new NotificationService(emailSender);notificationService.NotifyUser("张三");// 输出:[邮件发送] 用户 张三 已收到通知}
}

优势

  • 依赖在对象创建时就必须传入,确保对象初始化后即可正常工作(避免空引用)。
  • 依赖不可变(readonly),避免运行时被篡改。
2. 属性注入(不推荐,仅特殊场景使用)

属性注入通过公共属性传递依赖,适合 “可选依赖”(非必须的功能),但可能导致对象创建后依赖未初始化的问题。

csharp

public class NotificationService
{// 属性注入:依赖通过属性设置(通常有默认值或允许为null)public IMessageSender MessageSender { get; set; } = new EmailSender(); // 默认值public void NotifyUser(string username){if (MessageSender == null)throw new InvalidOperationException("未设置消息发送器");MessageSender.Send($"用户 {username} 已收到通知");}
}
// 调用
class Program
{static void Main(){var service = new NotificationService();service.MessageSender = new SmsSender(); // 通过属性注入依赖service.NotifyUser("李四");// 输出:[短信发送] 用户 李四 已收到通知}
}

注意:属性注入可能导致 “对象已创建但依赖未设置” 的风险,除非有明确理由(如框架限制),否则优先用构造函数注入。

二、使用 .NET 自带的 DI 容器(Microsoft.Extensions.DependencyInjection

在 .NET Core/.NET 5+ 中,官方提供了 Microsoft.Extensions.DependencyInjection 容器,是企业级开发的首选。需先通过 NuGet 安装包(一般项目默认已引用):Install-Package Microsoft.Extensions.DependencyInjection

1. 基本用法(注册 + 解析)

csharp

using Microsoft.Extensions.DependencyInjection;
using System;
// 复用上面的 IMessageSender、EmailSender、SmsSender、NotificationService
class Program
{static void Main(){// 1. 创建服务容器var serviceCollection = new ServiceCollection();// 2. 注册服务(关键步骤:告诉容器“抽象 -> 具体实现”的映射)// 注册 IMessageSender,指定实现为 EmailSenderserviceCollection.AddSingleton();// 注册 NotificationService(容器会自动注入其依赖 IMessageSender)serviceCollection.AddSingleton();// 3. 构建服务提供器(容器的具体实现)var serviceProvider = serviceCollection.BuildServiceProvider();// 4. 从容器解析服务(自动处理依赖链)var notificationService = serviceProvider.GetRequiredService();// 5. 使用服务notificationService.NotifyUser("王五");// 输出:[邮件发送] 用户 王五 已收到通知}
}
2. 服务生命周期(3 种核心类型)

容器通过 “生命周期” 管理服务实例的创建和销毁,核心有 3 种:

生命周期说明适用场景
Transient每次请求(GetService)创建新实例轻量级、无状态服务(如工具类)
Scoped每个 “作用域” 内创建一个实例(如 Web 请求)数据库上下文(DbContext)
Singleton整个应用生命周期内只创建一个实例全局配置、缓存服务

示例:验证生命周期差异

csharp

using Microsoft.Extensions.DependencyInjection;
using System;
// 测试服务:记录实例ID,观察是否为同一实例
public class TestService
{public Guid Id { get; } = Guid.NewGuid(); // 实例化时生成唯一ID
}
class Program
{static void Main(){var services = new ServiceCollection();// 注册3种生命周期的服务services.AddTransient(); // Transientservices.AddScoped();    // Scoped(需在作用域内解析)services.AddSingleton(); // Singletonvar provider = services.BuildServiceProvider();// 1. 测试 Transient:每次获取都是新实例Console.WriteLine("Transient:");var t1 = provider.GetRequiredService();var t2 = provider.GetRequiredService();Console.WriteLine($"t1.Id == t2.Id? {t1.Id == t2.Id}"); // 输出:False// 2. 测试 Scoped:同一作用域内是同一实例,不同作用域不同Console.WriteLine("\nScoped:");using (var scope1 = provider.CreateScope()) // 创建作用域1{var s1 = scope1.ServiceProvider.GetRequiredService();var s2 = scope1.ServiceProvider.GetRequiredService();Console.WriteLine($"scope1内 s1.Id == s2.Id? {s1.Id == s2.Id}"); // True}using (var scope2 = provider.CreateScope()) // 创建作用域2{var s3 = scope2.ServiceProvider.GetRequiredService();Console.WriteLine($"scope2内 s3.Id 与 scope1的s1不同? {true}"); // 必然不同}// 3. 测试 Singleton:全局唯一实例Console.WriteLine("\nSingleton:");var s1 = provider.GetRequiredService();var s2 = provider.GetRequiredService();Console.WriteLine($"s1.Id == s2.Id? {s1.Id == s2.Id}"); // 输出:True}
}
3. 依赖链解析(多层依赖)

容器会自动解析 “依赖的依赖”(递归解析),无需手动处理多层依赖关系。

csharp

using Microsoft.Extensions.DependencyInjection;
using System;
// 第一层依赖:日志服务
public interface ILogger { void Log(string msg); }
public class ConsoleLogger : ILogger { public void Log(string msg) => Console.WriteLine($"[日志] {msg}"); }
// 第二层依赖:用户仓储(依赖日志)
public interface IUserRepository
{void Add(string username);
}
public class UserRepository : IUserRepository
{private readonly ILogger _logger;// 依赖 ILoggerpublic UserRepository(ILogger logger){_logger = logger;}public void Add(string username){_logger.Log($"用户 {username} 已添加到数据库");}
}
// 第三层依赖:用户服务(依赖用户仓储)
public class UserService
{private readonly IUserRepository _userRepository;// 依赖 IUserRepository(其内部又依赖 ILogger)public UserService(IUserRepository userRepository){_userRepository = userRepository;}public void RegisterUser(string username){_userRepository.Add(username);}
}
// 容器自动解析依赖链
class Program
{static void Main(){var services = new ServiceCollection();// 注册所有依赖(只需注册抽象与实现的映射,容器自动处理依赖链)services.AddSingleton();services.AddSingleton();services.AddSingleton();var provider = services.BuildServiceProvider();var userService = provider.GetRequiredService();userService.RegisterUser("赵六");// 输出:[日志] 用户 赵六 已添加到数据库(依赖链:UserService -> UserRepository -> ConsoleLogger)}
}

三、ASP.NET Core 中的依赖注入(实际应用)

在 ASP.NET Core 中,DI 容器已内置,通常在 Program.cs 中注册服务,在控制器 / 服务中通过构造函数注入使用。

1. 注册服务(Program.cs)

csharp

var builder = WebApplication.CreateBuilder(args);
// 注册控制器(默认已包含)
builder.Services.AddControllers();
// 注册自定义服务
builder.Services.AddScoped(); // 作用域生命周期(适合Web请求)
builder.Services.AddScoped();
var app = builder.Build();
// 中间件配置...
app.MapControllers();
app.Run();
2. 在控制器中注入服务

csharp

using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class NotificationController : ControllerBase
{private readonly NotificationService _notificationService;// 控制器构造函数注入:容器自动传入 NotificationService(及其依赖)public NotificationController(NotificationService notificationService){_notificationService = notificationService;}[HttpGet("{username}")]public IActionResult Notify(string username){_notificationService.NotifyUser(username);return Ok("通知已发送");}
}

总结

  • 核心方式:构造函数注入是首选,确保依赖不可变且初始化完整。
  • 容器使用:.NET 自带的 Microsoft.Extensions.DependencyInjection 是主流选择,通过 “注册 - 解析” 管理服务,自动处理依赖链。
  • 生命周期:根据服务特性选择 Transient/Scoped/Singleton,避免因生命周期错误导致的问题(如在 Singleton 中注入 Scoped 服务)。

以上示例覆盖了从基础手动注入到框架级容器使用的场景,可根据项目规模选择合适的方式。

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

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

相关文章

mns 1115

A 简单题,一眼秒了。详情见 洛谷题解与第一个评论 B throw std::bad_brain("syncing failed"); throw std::bad_statement("可以根据询问的结果来决定策略"); 总之由于我一开始认为策略必须是固定…

Python遍历pandas数据方法总结

一、使用 iterrows() iterrows() 是 pandas 中最常用的遍历 DataFrame 行的方法之一。它将 DataFrame 的每一行作为一个 Series 进行迭代。 import pandas as pd def iterrows_example(): # 创建一个示例 DataFrame da…

2025 年 11 月温州电商财税律师,温州执行律师,温州法律顾问律师最新推荐,聚焦资质、案例、售后的七家机构深度解读

在商业活动日益复杂的当下,专业的法律支持成为企业与个人规避风险、保障权益的关键。无论是电商企业面临的财税合规难题、执行案件中的财产处置困境,还是企业日常运营所需的法律顾问服务,优质的律师资源都能提供核心…

P2966 [USACO09DEC] Cow Toll Paths G 题解

P2966 [USACO09DEC] Cow Toll Paths G 题解Floyd只有50pts,原因竟是......P2966 [USACO09DEC] Cow Toll Paths G 题解 题目链接 我的博客 思路 首先看数据范围。发现 \(1 \leq n \leq 250\),此题还是一个最短路问题,…

【System Beats!】第八章 异常控制流

异常控制流 控制流与异常控制流控制流:一条条指令的执行顺序 从一条指令到下一条指令的过渡称为控制转移。 控制转移的常见类型:跳转、分支、调用、返回,即对程序状态的变化作出反应。 现代系统通过使控制流发生突变…

2025 年 11 月温州法律顾问律师,温州婚姻律师,温州刑事律师最新推荐,聚焦资质、案例、售后的五家机构深度解读

在法律需求日益多元化的当下,专业的法律顾问、婚姻家事及刑事辩护服务成为保障权益的关键。本次测评由行业协会联合第三方机构开展,覆盖 32 家专业机构,通过资质审核、案例复盘、客户满意度调研、售后响应速度四大维…

oracle 优化

1、SQL2、会话 v$session v$sesstat v$session_waitv$sql v$lock sql_trace3、系统 awr

2025 年筛选机厂家推荐:深圳市恩艾斯科技有限公司,光学筛选机的专业缔造者与行业深耕者

在当下工业智能化浪潮中,智能检测作为提升生产效率与产品质量的关键环节,地位愈发凸显。机器视觉检测技术凭借其高精度、高效率等特性,成为众多制造业企业实现自动化升级的首选方案。在此蓬勃发展的行业背景下,深圳…

人工智能之编程基础 Python 入门:第六章 基本数据类型(二)

人工智能之编程基础 Python 入门:第六章 基本数据类型(二)人工智能之编程基础 Python 入门 第六章 基础数据类型(二)@目录人工智能之编程基础 Python 入门前言List(列表)1. 列表的创建2. 列表的特性3. 列表的访…

2025年跨境电商ERP系统权威推荐:赛狐ERP系统适配多平台、多站点智能化管理,跨境电商卖家首选

面对跨境电商管理的复杂性,一套高效的ERP系统能提升平均30%的运营效率,降低25%的库存成本。 随着全球跨境电商市场规模持续扩大,跨境电商运营复杂度不断增加。ERP系统作为跨境电商管理的核心工具,其选型直接关系到…

黄景行电脑软件简介

黄景行电脑软件简介黄景行电脑软件 说明 2025-11-15创建日期2013since 2013 主页 https://hjxdn.my.canvasite.cn

P14507 缺零分治 mexdnc

P14507 缺零分治 mexdnc 题解题目传送门 更阅读体验的阅读体验暴力做法一 我们发现,如果当前 \(a\) 里面没有 0,那么当询问里有 0 时,直接把所有数装进一个集合就可以了。如果询问不是 0,那么无论如何分集合都不能…

2025年共享观光车厂家权威推荐榜单:封闭式观光车/电动观光车/电动游览车源头厂家精选

在共享经济与绿色出行理念深度融合的背景下,2025年中国电动观光车市场规模预计达125亿元,共享模式正成为景区、园区交通解决方案的新趋势。 共享观光车通过分时租赁、智能调度和集约化运营,有效降低了运营方的初次采…

对拍程序

对拍的模板duipai.cpp ↓↓↓ #include<bits/stdc++.h> using namespace std; #define ll long long int main() {system("g++ rand.cpp -o rand");system("g++ my.cpp -o my");system(&quo…

反编译通用流程

反编译通用流程静态分析 用IDA脚本反编译导出所有的代码。 将代码由大到小排序。先反编译大的代码使用claude。因为大的代码更有可能是关键函数。这里我用的是evol claude工具生成的代码。或者https://lmarena.ai/c/01…

2025 年 11 月超细碳酸钙,重钙,碳酸钙厂家最新推荐,产能、专利、环保三维数据透视!

在新材料产业高速发展的当下,超细碳酸钙、重钙、碳酸钙作为造纸、橡胶、塑料、医药等多领域核心添加剂,其品质直接决定终端产品竞争力。然而市场乱象频发:部分厂家工艺落后导致产品纯度不足、性能不稳定,部分供应商…

20251115 - CAN协议层梳理【不含电气特性介绍】

CAN协议 1. 帧的种类 CAN通信是通过以下5种类型的帧进行的。数据帧 遥控帧 错误帧 过载帧 帧间隔其中,数据帧和遥控帧有标准格式(11位标识符,即ID)和扩展格式(29位ID)这两种格式。2.数据帧的组成:在标准格式中,…

校准仪

校准仪https://www.fluke.com.cn/product/%E8%BF%87%E7%A8%8B%E6%A0%A1%E5%87%86%E5%8F%8A%E6%A3%80%E6%B5%8B%E5%B7%A5%E5%85%B7/%E5%A4%9A%E5%8A%9F%E8%83%BD%E8%BF%87%E7%A8%8B%E6%A0%A1%E9%AA%8C%E4%BB%AA/5500x#

从工具理性到价值共生:开源链动2+1模式、AI智能名片与S2B2C商城体系的社会连接重构研究

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

聚焦成都留学服务:藤校申请、语言培训、就业规划一站式解决,2025优质机构榜单出炉

聚焦成都留学服务:藤校申请、语言培训、就业规划一站式解决,2025优质机构榜单出炉随着成都国际化进程加速,出国留学需求逐年攀升,优质的留学机构成为学子实现海外求学梦的重要支撑。专业留学机构不仅能提供精准的院…