前面介绍过 什么是控制反转(ioc)?什么是依赖注入(di)?以及实现原理
在 c# 中,依赖注入(di)的实现方式主要分为手动注入和通过 ioc 容器注入(如 .net 自带的 microsoft.extensions.dependencyinjection)。以下是具体代码示例,涵盖常用场景和最佳实践。
一、手动依赖注入(基础示例)
手动注入不依赖第三方容器,直接通过代码传递依赖,适合简单场景,核心是构造函数注入(最推荐的方式)。
1. 构造函数注入(推荐)
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. 属性注入(不推荐,仅特殊场景使用)
属性注入通过公共属性传递依赖,适合 “可选依赖”(非必须的功能),但可能导致对象创建后依赖未初始化的问题。
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. 基本用法(注册 + 解析)
using microsoft.extensions.dependencyinjection;
using system;
// 复用上面的 imessagesender、emailsender、smssender、notificationservice
class program
{
static void main()
{
// 1. 创建服务容器
var servicecollection = new servicecollection();
// 2. 注册服务(关键步骤:告诉容器“抽象 -> 具体实现”的映射)
// 注册 imessagesender,指定实现为 emailsender
servicecollection.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 | 整个应用生命周期内只创建一个实例 | 全局配置、缓存服务 |
示例:验证生命周期差异
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>(); // transient
services.addscoped<testservice>(); // scoped(需在作用域内解析)
services.addsingleton<testservice>(); // singleton
var 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. 依赖链解析(多层依赖)
容器会自动解析 “依赖的依赖”(递归解析),无需手动处理多层依赖关系。
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;
// 依赖 ilogger
public 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.netcore 中的依赖注入(实际应用)
在 asp.net core 中,di 容器已内置,通常在 program.cs 中注册服务,在控制器 / 服务中通过构造函数注入使用。
1. 注册服务(program.cs)
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. 在控制器中注入服务
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 服务)。
以上示例覆盖了从基础手动注入到框架级容器使用的场景,可根据项目规模选择合适的方式。
到此这篇关于c# 中依赖注入(di)的实现方式的文章就介绍到这了,更多相关c# 依赖注入内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论