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

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

前面介绍过 什么是控制反转(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、NotificationServiceclass Program
{static void Main(){// 1. 创建服务容器var serviceCollection = new ServiceCollection();// 2. 注册服务(关键步骤:告诉容器“抽象 -> 具体实现”的映射)// 注册 IMessageSender,指定实现为 EmailSenderserviceCollection.AddSingleton<IMessageSender, EmailSender>();// 注册 NotificationService(容器会自动注入其依赖 IMessageSender)serviceCollection.AddSingleton<NotificationService>();// 3. 构建服务提供器(容器的具体实现)var serviceProvider = serviceCollection.BuildServiceProvider();// 4. 从容器解析服务(自动处理依赖链)var notificationService = serviceProvider.GetRequiredService<NotificationService>();// 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<TestService>(); // Transientservices.AddScoped<TestService>();    // Scoped(需在作用域内解析)services.AddSingleton<TestService>(); // Singletonvar provider = services.BuildServiceProvider();// 1. 测试 Transient:每次获取都是新实例Console.WriteLine("Transient:");var t1 = provider.GetRequiredService<TestService>();var t2 = provider.GetRequiredService<TestService>();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<TestService>();var s2 = scope1.ServiceProvider.GetRequiredService<TestService>();Console.WriteLine($"scope1内 s1.Id == s2.Id? {s1.Id == s2.Id}"); // True}using (var scope2 = provider.CreateScope()) // 创建作用域2{var s3 = scope2.ServiceProvider.GetRequiredService<TestService>();Console.WriteLine($"scope2内 s3.Id 与 scope1的s1不同? {true}"); // 必然不同}// 3. 测试 Singleton:全局唯一实例Console.WriteLine("\nSingleton:");var s1 = provider.GetRequiredService<TestService>();var s2 = provider.GetRequiredService<TestService>();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<ILogger, ConsoleLogger>();services.AddSingleton<IUserRepository, UserRepository>();services.AddSingleton<UserService>();var provider = services.BuildServiceProvider();var userService = provider.GetRequiredService<UserService>();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<IMessageSender, EmailSender>(); // 作用域生命周期(适合Web请求)
builder.Services.AddScoped<NotificationService>();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.dtcms.com/a/499400.html

相关文章:

  • java微服务驱动的社区平台:友猫社区的功能模块与实现逻辑
  • Flask入门教程——李辉 第三章 关键知识梳理
  • 产品更新与重构策略:创新与稳定的平衡之道
  • 【微服务】(1) Spring Cloud 概述
  • 做外贸球衣用什么网站嘉兴做微网站
  • 京华建设科技有限公司网站中华建筑网校
  • 合肥市高新区2025年初中信息学竞赛试题T1-T4 C++ 有故事听[doge]
  • Day 13 root 相关说明--以 ANAEX01 为实例
  • [Linux]学习笔记系列 -- [kernel][lock]debug_locks
  • Linux中双向链表介绍
  • 建设网站的运行费包括什么地方企业做网站哪家公司好
  • 产品频繁重构:企业发展的双刃剑
  • 微软Win11双AI功能来袭:“AI管家”+聊天机器人重构桌面交互体验
  • 2025年SEVC SCI2区,改进混沌多元宇宙算法+可重构作业车间物料配送优化,深度解析+性能实测,深度解析+性能实测
  • 建设网站的网站底压电工证wordpress导航主题模板下载地址
  • 自己做的网站怎么弄成app包装产品做网站
  • [GO]GORM中的Tag映射规则
  • 网站建设全包公司推荐山东大学青岛校区建设指挥部网站
  • P8611 蚂蚁感冒
  • 网站服务器知识平远县建设工程交易中心网站
  • 支付宝沙箱环境和正式环境
  • 【硬件基础】自用——二极管の配图
  • 天津企业模板建站哪个好wordpress可视化编辑主题
  • 网站配置到iis后读不了数据室内设计平面图简单
  • 扭蛋机 Roll 福利房小程序前端功能设计:融合趣味互动与福利适配
  • 认识mysql
  • PostgreSQL与MySQL对比小结
  • 数据结构与使用
  • Redis位域详细介绍
  • 破解高度差光学镜片检测难题,景深融合 AI 方案让制造更高效