C#中的依赖注入:是什么以及为什么需要它
Dependency Injection (DI) 是一种关键的设计模式,它允许在C#中创建松耦合且易于测试的应用程序。类不再自己创建其依赖项(例如,用于数据库操作或日志记录的对象),而是从外部传递(注入)这些依赖项。这实现了控制反转 (IoC) 原则——控制权被移交给外部容器。
在.NET生态系统中,从ASP.NET Core开始,DI已成为事实上的标准。内置的依赖注入容器(通过Microsoft.Extensions.DependencyInjection)可以轻松注册和解析依赖项。在本文中,我们将探讨DI的理论、实际示例以及C#中的最佳实践。
依赖注入的基本原则
DI基于三种基本的依赖注入方式。我们来逐一分析。
1. 构造函数注入
这是最常见且首选的方式。依赖项通过类的构造函数参数传递。
public interface ILogger{ void Log(string message);}
public class ConsoleLogger : ILogger{ public void Log(string message) => Console.WriteLine(message);}
public class OrderService{ private readonly ILogger _logger;
// 通过构造函数注入依赖项 public OrderService(ILogger logger) { _logger = logger; }
public void ProcessOrder(int orderId) { _logger.Log($"处理订单 {orderId}"); // 业务逻辑 }}2. 属性注入
依赖项通过公共属性设置。这种方式不太推荐,因为它使依赖项变为可选,并可能导致运行时错误。
public class OrderService{ public ILogger Logger { get; set; }
public void ProcessOrder(int orderId) { if (Logger == null) throw new InvalidOperationException("Logger未设置"); Logger.Log($"处理订单 {orderId}"); }}3. 方法注入
依赖项作为方法参数传递。很少使用,通常用于临时依赖项。
public class OrderService{ public void ProcessOrder(int orderId, ILogger logger) { logger.Log($"处理订单 {orderId}"); }}在.NET容器中注册依赖项
在现代.NET(ASP.NET Core、控制台应用、Worker Services)中,使用内置的DI容器。注册发生在ConfigureServices方法中,或者在创建ServiceCollection时进行。
已注册的服务有三种生命周期:
- Transient — 每次请求时都会创建一个新实例。适用于轻量级、短生命周期的服务。
- Scoped — 每个HTTP请求(或作用域)一个实例。用于Entity Framework的DbContext。
- Singleton — 整个应用程序一个实例。适用于缓存、配置、日志记录器。
// ASP.NET Core示例public class Startup{ public void ConfigureServices(IServiceCollection services) { // 使用接口注册 services.AddTransient<ILogger, ConsoleLogger>(); services.AddScoped<IOrderRepository, OrderRepository>(); services.AddSingleton<IConfiguration, ConfigurationManager>();
// 不使用接口注册(具体类) services.AddTransient<OrderService>(); }}
// 在控制器中使用public class OrderController : ControllerBase{ private readonly OrderService _orderService;
public OrderController(OrderServi