C# 中依賴(lài)注入(DI)的實(shí)現(xiàn)方式
前面介紹過(guò) 什么是控制反轉(zhuǎn)(IoC)?什么是依賴(lài)注入(DI)?以及實(shí)現(xiàn)原理
在 C# 中,依賴(lài)注入(DI)的實(shí)現(xiàn)方式主要分為手動(dòng)注入和通過(guò) IoC 容器注入(如 .NET 自帶的 Microsoft.Extensions.DependencyInjection)。以下是具體代碼示例,涵蓋常用場(chǎng)景和最佳實(shí)踐。
一、手動(dòng)依賴(lài)注入(基礎(chǔ)示例)
手動(dòng)注入不依賴(lài)第三方容器,直接通過(guò)代碼傳遞依賴(lài),適合簡(jiǎn)單場(chǎng)景,核心是構(gòu)造函數(shù)注入(最推薦的方式)。
1. 構(gòu)造函數(shù)注入(推薦)
using System;
// 1. 定義抽象依賴(lài)(接口)
public interface IMessageSender
{
void Send(string message);
}
// 2. 實(shí)現(xiàn)具體依賴(lài)(郵件發(fā)送)
public class EmailSender : IMessageSender
{
public void Send(string message)
{
Console.WriteLine($"[郵件發(fā)送] {message}");
}
}
// 3. 實(shí)現(xiàn)具體依賴(lài)(短信發(fā)送)
public class SmsSender : IMessageSender
{
public void Send(string message)
{
Console.WriteLine($"[短信發(fā)送] {message}");
}
}
// 4. 依賴(lài)方(通知服務(wù)):通過(guò)構(gòu)造函數(shù)接收依賴(lài)
public class NotificationService
{
private readonly IMessageSender _messageSender;
// 構(gòu)造函數(shù)注入:依賴(lài)由外部傳入,且用 readonly 確保不可變
public NotificationService(IMessageSender messageSender)
{
_messageSender = messageSender ?? throw new ArgumentNullException(
nameof(messageSender), "依賴(lài)不能為空"); // 校驗(yàn)依賴(lài),避免空引用
}
public void NotifyUser(string username)
{
_messageSender.Send($"用戶(hù) {username} 已收到通知");
}
}
// 5. 調(diào)用:手動(dòng)注入依賴(lài)
class Program
{
static void Main()
{
// 手動(dòng)創(chuàng)建依賴(lài)實(shí)例
IMessageSender emailSender = new EmailSender();
// IMessageSender smsSender = new SmsSender(); // 可切換為短信發(fā)送
// 注入到依賴(lài)方
NotificationService notificationService = new NotificationService(emailSender);
notificationService.NotifyUser("張三");
// 輸出:[郵件發(fā)送] 用戶(hù) 張三 已收到通知
}
}
優(yōu)勢(shì):
- 依賴(lài)在對(duì)象創(chuàng)建時(shí)就必須傳入,確保對(duì)象初始化后即可正常工作(避免空引用)。
- 依賴(lài)不可變(
readonly),避免運(yùn)行時(shí)被篡改。
2. 屬性注入(不推薦,僅特殊場(chǎng)景使用)
屬性注入通過(guò)公共屬性傳遞依賴(lài),適合 “可選依賴(lài)”(非必須的功能),但可能導(dǎo)致對(duì)象創(chuàng)建后依賴(lài)未初始化的問(wèn)題。
public class NotificationService
{
// 屬性注入:依賴(lài)通過(guò)屬性設(shè)置(通常有默認(rèn)值或允許為null)
public IMessageSender MessageSender { get; set; } = new EmailSender(); // 默認(rèn)值
public void NotifyUser(string username)
{
if (MessageSender == null)
throw new InvalidOperationException("未設(shè)置消息發(fā)送器");
MessageSender.Send($"用戶(hù) {username} 已收到通知");
}
}
// 調(diào)用
class Program
{
static void Main()
{
var service = new NotificationService();
service.MessageSender = new SmsSender(); // 通過(guò)屬性注入依賴(lài)
service.NotifyUser("李四");
// 輸出:[短信發(fā)送] 用戶(hù) 李四 已收到通知
}
}
注意:屬性注入可能導(dǎo)致 “對(duì)象已創(chuàng)建但依賴(lài)未設(shè)置” 的風(fēng)險(xiǎn),除非有明確理由(如框架限制),否則優(yōu)先用構(gòu)造函數(shù)注入。
二、使用 .NET 自帶的 DI 容器(Microsoft.Extensions.DependencyInjection)
在 .NET Core/.NET 5+ 中,官方提供了 Microsoft.Extensions.DependencyInjection 容器,是企業(yè)級(jí)開(kāi)發(fā)的首選。需先通過(guò) NuGet 安裝包(一般項(xiàng)目默認(rèn)已引用):Install-Package Microsoft.Extensions.DependencyInjection
1. 基本用法(注冊(cè) + 解析)
using Microsoft.Extensions.DependencyInjection;
using System;
// 復(fù)用上面的 IMessageSender、EmailSender、SmsSender、NotificationService
class Program
{
static void Main()
{
// 1. 創(chuàng)建服務(wù)容器
var serviceCollection = new ServiceCollection();
// 2. 注冊(cè)服務(wù)(關(guān)鍵步驟:告訴容器“抽象 -> 具體實(shí)現(xiàn)”的映射)
// 注冊(cè) IMessageSender,指定實(shí)現(xiàn)為 EmailSender
serviceCollection.AddSingleton<IMessageSender, EmailSender>();
// 注冊(cè) NotificationService(容器會(huì)自動(dòng)注入其依賴(lài) IMessageSender)
serviceCollection.AddSingleton<NotificationService>();
// 3. 構(gòu)建服務(wù)提供器(容器的具體實(shí)現(xiàn))
var serviceProvider = serviceCollection.BuildServiceProvider();
// 4. 從容器解析服務(wù)(自動(dòng)處理依賴(lài)鏈)
var notificationService = serviceProvider.GetRequiredService<NotificationService>();
// 5. 使用服務(wù)
notificationService.NotifyUser("王五");
// 輸出:[郵件發(fā)送] 用戶(hù) 王五 已收到通知
}
}
2. 服務(wù)生命周期(3 種核心類(lèi)型)
容器通過(guò) “生命周期” 管理服務(wù)實(shí)例的創(chuàng)建和銷(xiāo)毀,核心有 3 種:
| 生命周期 | 說(shuō)明 | 適用場(chǎng)景 |
|---|---|---|
| Transient | 每次請(qǐng)求(GetService)創(chuàng)建新實(shí)例 | 輕量級(jí)、無(wú)狀態(tài)服務(wù)(如工具類(lèi)) |
| Scoped | 每個(gè) “作用域” 內(nèi)創(chuàng)建一個(gè)實(shí)例(如 Web 請(qǐng)求) | 數(shù)據(jù)庫(kù)上下文(DbContext) |
| Singleton | 整個(gè)應(yīng)用生命周期內(nèi)只創(chuàng)建一個(gè)實(shí)例 | 全局配置、緩存服務(wù) |
示例:驗(yàn)證生命周期差異
using Microsoft.Extensions.DependencyInjection;
using System;
// 測(cè)試服務(wù):記錄實(shí)例ID,觀察是否為同一實(shí)例
public class TestService
{
public Guid Id { get; } = Guid.NewGuid(); // 實(shí)例化時(shí)生成唯一ID
}
class Program
{
static void Main()
{
var services = new ServiceCollection();
// 注冊(cè)3種生命周期的服務(wù)
services.AddTransient<TestService>(); // Transient
services.AddScoped<TestService>(); // Scoped(需在作用域內(nèi)解析)
services.AddSingleton<TestService>(); // Singleton
var provider = services.BuildServiceProvider();
// 1. 測(cè)試 Transient:每次獲取都是新實(shí)例
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. 測(cè)試 Scoped:同一作用域內(nèi)是同一實(shí)例,不同作用域不同
Console.WriteLine("\nScoped:");
using (var scope1 = provider.CreateScope()) // 創(chuàng)建作用域1
{
var s1 = scope1.ServiceProvider.GetRequiredService<TestService>();
var s2 = scope1.ServiceProvider.GetRequiredService<TestService>();
Console.WriteLine($"scope1內(nèi) s1.Id == s2.Id? {s1.Id == s2.Id}"); // True
}
using (var scope2 = provider.CreateScope()) // 創(chuàng)建作用域2
{
var s3 = scope2.ServiceProvider.GetRequiredService<TestService>();
Console.WriteLine($"scope2內(nèi) s3.Id 與 scope1的s1不同? {true}"); // 必然不同
}
// 3. 測(cè)試 Singleton:全局唯一實(shí)例
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. 依賴(lài)鏈解析(多層依賴(lài))
容器會(huì)自動(dòng)解析 “依賴(lài)的依賴(lài)”(遞歸解析),無(wú)需手動(dòng)處理多層依賴(lài)關(guān)系。
using Microsoft.Extensions.DependencyInjection;
using System;
// 第一層依賴(lài):日志服務(wù)
public interface ILogger { void Log(string msg); }
public class ConsoleLogger : ILogger { public void Log(string msg) => Console.WriteLine($"[日志] {msg}"); }
// 第二層依賴(lài):用戶(hù)倉(cāng)儲(chǔ)(依賴(lài)日志)
public interface IUserRepository
{
void Add(string username);
}
public class UserRepository : IUserRepository
{
private readonly ILogger _logger;
// 依賴(lài) ILogger
public UserRepository(ILogger logger)
{
_logger = logger;
}
public void Add(string username)
{
_logger.Log($"用戶(hù) {username} 已添加到數(shù)據(jù)庫(kù)");
}
}
// 第三層依賴(lài):用戶(hù)服務(wù)(依賴(lài)用戶(hù)倉(cāng)儲(chǔ))
public class UserService
{
private readonly IUserRepository _userRepository;
// 依賴(lài) IUserRepository(其內(nèi)部又依賴(lài) ILogger)
public UserService(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public void RegisterUser(string username)
{
_userRepository.Add(username);
}
}
// 容器自動(dòng)解析依賴(lài)鏈
class Program
{
static void Main()
{
var services = new ServiceCollection();
// 注冊(cè)所有依賴(lài)(只需注冊(cè)抽象與實(shí)現(xiàn)的映射,容器自動(dòng)處理依賴(lài)鏈)
services.AddSingleton<ILogger, ConsoleLogger>();
services.AddSingleton<IUserRepository, UserRepository>();
services.AddSingleton<UserService>();
var provider = services.BuildServiceProvider();
var userService = provider.GetRequiredService<UserService>();
userService.RegisterUser("趙六");
// 輸出:[日志] 用戶(hù) 趙六 已添加到數(shù)據(jù)庫(kù)(依賴(lài)鏈:UserService -> UserRepository -> ConsoleLogger)
}
}
三、ASP.NETCore 中的依賴(lài)注入(實(shí)際應(yīng)用)
在 ASP.NET Core 中,DI 容器已內(nèi)置,通常在 Program.cs 中注冊(cè)服務(wù),在控制器 / 服務(wù)中通過(guò)構(gòu)造函數(shù)注入使用。
1. 注冊(cè)服務(wù)(Program.cs)
var builder = WebApplication.CreateBuilder(args); // 注冊(cè)控制器(默認(rèn)已包含) builder.Services.AddControllers(); // 注冊(cè)自定義服務(wù) builder.Services.AddScoped<IMessageSender, EmailSender>(); // 作用域生命周期(適合Web請(qǐng)求) builder.Services.AddScoped<NotificationService>(); var app = builder.Build(); // 中間件配置... app.MapControllers(); app.Run();
2. 在控制器中注入服務(wù)
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class NotificationController : ControllerBase
{
private readonly NotificationService _notificationService;
// 控制器構(gòu)造函數(shù)注入:容器自動(dòng)傳入 NotificationService(及其依賴(lài))
public NotificationController(NotificationService notificationService)
{
_notificationService = notificationService;
}
[HttpGet("{username}")]
public IActionResult Notify(string username)
{
_notificationService.NotifyUser(username);
return Ok("通知已發(fā)送");
}
}
總結(jié)
- 核心方式:構(gòu)造函數(shù)注入是首選,確保依賴(lài)不可變且初始化完整。
- 容器使用:.NET 自帶的
Microsoft.Extensions.DependencyInjection是主流選擇,通過(guò) “注冊(cè) - 解析” 管理服務(wù),自動(dòng)處理依賴(lài)鏈。 - 生命周期:根據(jù)服務(wù)特性選擇 Transient/Scoped/Singleton,避免因生命周期錯(cuò)誤導(dǎo)致的問(wèn)題(如在 Singleton 中注入 Scoped 服務(wù))。
以上示例覆蓋了從基礎(chǔ)手動(dòng)注入到框架級(jí)容器使用的場(chǎng)景,可根據(jù)項(xiàng)目規(guī)模選擇合適的方式。
到此這篇關(guān)于C# 中依賴(lài)注入(DI)的實(shí)現(xiàn)方式的文章就介紹到這了,更多相關(guān)C# 依賴(lài)注入內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#實(shí)現(xiàn)鐘表程序設(shè)計(jì)
這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)鐘表程序設(shè)計(jì),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06
基于WinForm實(shí)現(xiàn)通用自動(dòng)更新系統(tǒng)的完整流程
在C/S架構(gòu)的應(yīng)用程序中,如何讓客戶(hù)端保持最新版本一直是個(gè)令人頭疼的問(wèn)題,每次更新都要通知用戶(hù)下載、安裝,不僅麻煩,還容易導(dǎo)致版本混亂,所以今天要介紹的,就是一個(gè)基于WinForm實(shí)現(xiàn)的通用自動(dòng)更新器方案,需要的朋友可以參考下2025-10-10
C#實(shí)現(xiàn)簡(jiǎn)易點(diǎn)餐功能
這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)簡(jiǎn)易點(diǎn)餐功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-07-07
C#實(shí)現(xiàn)從位圖到布隆過(guò)濾器的方法
布隆過(guò)濾器(Bloom filter)是一種特殊的 Hash Table,能夠以較小的存儲(chǔ)空間較快地判斷出數(shù)據(jù)是否存在。常用于允許一定誤判率的數(shù)據(jù)過(guò)濾及防止緩存擊穿及等場(chǎng)景,本文將以 C# 語(yǔ)言來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的布隆過(guò)濾器,為簡(jiǎn)化說(shuō)明,設(shè)計(jì)得很簡(jiǎn)單,需要的朋友可以參考下2022-06-06
C#導(dǎo)航器Xpath與XPathNavigator類(lèi)
這篇文章介紹了C#導(dǎo)航器Xpath與XPathNavigator類(lèi),文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06
C#啟動(dòng)和停止windows服務(wù)的實(shí)例代碼
這篇文章介紹了C#啟動(dòng)和停止windows服務(wù)的實(shí)例代碼,有需要的朋友可以參考一下2013-09-09
C# Winfom 中ListBox的簡(jiǎn)單用法詳解
這篇文章主要介紹了C# Winfom 中ListBox的簡(jiǎn)單用法詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12

