C# 中依賴注入(DI)的實(shí)現(xiàn)方式
前面介紹過 什么是控制反轉(zhuǎn)(IoC)?什么是依賴注入(DI)?以及實(shí)現(xiàn)原理
在 C# 中,依賴注入(DI)的實(shí)現(xiàn)方式主要分為手動(dòng)注入和通過 IoC 容器注入(如 .NET 自帶的 Microsoft.Extensions.DependencyInjection)。以下是具體代碼示例,涵蓋常用場(chǎng)景和最佳實(shí)踐。
一、手動(dòng)依賴注入(基礎(chǔ)示例)
手動(dòng)注入不依賴第三方容器,直接通過代碼傳遞依賴,適合簡(jiǎn)單場(chǎng)景,核心是構(gòu)造函數(shù)注入(最推薦的方式)。
1. 構(gòu)造函數(shù)注入(推薦)
using System;
// 1. 定義抽象依賴(接口)
public interface IMessageSender
{
void Send(string message);
}
// 2. 實(shí)現(xiàn)具體依賴(郵件發(fā)送)
public class EmailSender : IMessageSender
{
public void Send(string message)
{
Console.WriteLine($"[郵件發(fā)送] {message}");
}
}
// 3. 實(shí)現(xiàn)具體依賴(短信發(fā)送)
public class SmsSender : IMessageSender
{
public void Send(string message)
{
Console.WriteLine($"[短信發(fā)送] {message}");
}
}
// 4. 依賴方(通知服務(wù)):通過構(gòu)造函數(shù)接收依賴
public class NotificationService
{
private readonly IMessageSender _messageSender;
// 構(gòu)造函數(shù)注入:依賴由外部傳入,且用 readonly 確保不可變
public NotificationService(IMessageSender messageSender)
{
_messageSender = messageSender ?? throw new ArgumentNullException(
nameof(messageSender), "依賴不能為空"); // 校驗(yàn)依賴,避免空引用
}
public void NotifyUser(string username)
{
_messageSender.Send($"用戶 {username} 已收到通知");
}
}
// 5. 調(diào)用:手動(dòng)注入依賴
class Program
{
static void Main()
{
// 手動(dòng)創(chuàng)建依賴實(shí)例
IMessageSender emailSender = new EmailSender();
// IMessageSender smsSender = new SmsSender(); // 可切換為短信發(fā)送
// 注入到依賴方
NotificationService notificationService = new NotificationService(emailSender);
notificationService.NotifyUser("張三");
// 輸出:[郵件發(fā)送] 用戶 張三 已收到通知
}
}
優(yōu)勢(shì):
- 依賴在對(duì)象創(chuàng)建時(shí)就必須傳入,確保對(duì)象初始化后即可正常工作(避免空引用)。
- 依賴不可變(
readonly),避免運(yùn)行時(shí)被篡改。
2. 屬性注入(不推薦,僅特殊場(chǎng)景使用)
屬性注入通過公共屬性傳遞依賴,適合 “可選依賴”(非必須的功能),但可能導(dǎo)致對(duì)象創(chuàng)建后依賴未初始化的問題。
public class NotificationService
{
// 屬性注入:依賴通過屬性設(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($"用戶 {username} 已收到通知");
}
}
// 調(diào)用
class Program
{
static void Main()
{
var service = new NotificationService();
service.MessageSender = new SmsSender(); // 通過屬性注入依賴
service.NotifyUser("李四");
// 輸出:[短信發(fā)送] 用戶 李四 已收到通知
}
}
注意:屬性注入可能導(dǎo)致 “對(duì)象已創(chuàng)建但依賴未設(shè)置” 的風(fēng)險(xiǎn),除非有明確理由(如框架限制),否則優(yōu)先用構(gòu)造函數(shù)注入。
二、使用 .NET 自帶的 DI 容器(Microsoft.Extensions.DependencyInjection)
在 .NET Core/.NET 5+ 中,官方提供了 Microsoft.Extensions.DependencyInjection 容器,是企業(yè)級(jí)開發(fā)的首選。需先通過 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)注入其依賴 IMessageSender)
serviceCollection.AddSingleton<NotificationService>();
// 3. 構(gòu)建服務(wù)提供器(容器的具體實(shí)現(xiàn))
var serviceProvider = serviceCollection.BuildServiceProvider();
// 4. 從容器解析服務(wù)(自動(dòng)處理依賴鏈)
var notificationService = serviceProvider.GetRequiredService<NotificationService>();
// 5. 使用服務(wù)
notificationService.NotifyUser("王五");
// 輸出:[郵件發(fā)送] 用戶 王五 已收到通知
}
}
2. 服務(wù)生命周期(3 種核心類型)
容器通過 “生命周期” 管理服務(wù)實(shí)例的創(chuàng)建和銷毀,核心有 3 種:
| 生命周期 | 說明 | 適用場(chǎng)景 |
|---|---|---|
| Transient | 每次請(qǐng)求(GetService)創(chuàng)建新實(shí)例 | 輕量級(jí)、無(wú)狀態(tài)服務(wù)(如工具類) |
| 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. 依賴鏈解析(多層依賴)
容器會(huì)自動(dòng)解析 “依賴的依賴”(遞歸解析),無(wú)需手動(dòng)處理多層依賴關(guān)系。
using Microsoft.Extensions.DependencyInjection;
using System;
// 第一層依賴:日志服務(wù)
public interface ILogger { void Log(string msg); }
public class ConsoleLogger : ILogger { public void Log(string msg) => Console.WriteLine($"[日志] {msg}"); }
// 第二層依賴:用戶倉(cāng)儲(chǔ)(依賴日志)
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} 已添加到數(shù)據(jù)庫(kù)");
}
}
// 第三層依賴:用戶服務(wù)(依賴用戶倉(cāng)儲(chǔ))
public class UserService
{
private readonly IUserRepository _userRepository;
// 依賴 IUserRepository(其內(nèi)部又依賴 ILogger)
public UserService(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public void RegisterUser(string username)
{
_userRepository.Add(username);
}
}
// 容器自動(dòng)解析依賴鏈
class Program
{
static void Main()
{
var services = new ServiceCollection();
// 注冊(cè)所有依賴(只需注冊(cè)抽象與實(shí)現(xiàn)的映射,容器自動(dòng)處理依賴鏈)
services.AddSingleton<ILogger, ConsoleLogger>();
services.AddSingleton<IUserRepository, UserRepository>();
services.AddSingleton<UserService>();
var provider = services.BuildServiceProvider();
var userService = provider.GetRequiredService<UserService>();
userService.RegisterUser("趙六");
// 輸出:[日志] 用戶 趙六 已添加到數(shù)據(jù)庫(kù)(依賴鏈:UserService -> UserRepository -> ConsoleLogger)
}
}
三、ASP.NETCore 中的依賴注入(實(shí)際應(yīng)用)
在 ASP.NET Core 中,DI 容器已內(nèi)置,通常在 Program.cs 中注冊(cè)服務(wù),在控制器 / 服務(wù)中通過構(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(及其依賴)
public NotificationController(NotificationService notificationService)
{
_notificationService = notificationService;
}
[HttpGet("{username}")]
public IActionResult Notify(string username)
{
_notificationService.NotifyUser(username);
return Ok("通知已發(fā)送");
}
}
總結(jié)
- 核心方式:構(gòu)造函數(shù)注入是首選,確保依賴不可變且初始化完整。
- 容器使用:.NET 自帶的
Microsoft.Extensions.DependencyInjection是主流選擇,通過 “注冊(cè) - 解析” 管理服務(wù),自動(dòng)處理依賴鏈。 - 生命周期:根據(jù)服務(wù)特性選擇 Transient/Scoped/Singleton,避免因生命周期錯(cuò)誤導(dǎo)致的問題(如在 Singleton 中注入 Scoped 服務(wù))。
以上示例覆蓋了從基礎(chǔ)手動(dòng)注入到框架級(jí)容器使用的場(chǎng)景,可根據(jù)項(xiàng)目規(guī)模選擇合適的方式。
到此這篇關(guān)于C# 中依賴注入(DI)的實(shí)現(xiàn)方式的文章就介紹到這了,更多相關(guān)C# 依賴注入內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#實(shí)現(xiàn)仿QQ抽屜式窗體的設(shè)計(jì)方法
QQ軟件對(duì)于絕大多數(shù)的人來(lái)說再熟悉不過了,它以使用方便、界面美觀及功能完善而著稱,本文給大家介紹了C#實(shí)現(xiàn)仿QQ抽屜式窗體的設(shè)計(jì)方法,主要通過使用API函數(shù)WindowFromPoint和GetParent實(shí)現(xiàn)仿QQ的抽屜式窗體,需要的朋友可以參考下2024-04-04

