一、为什么需要依赖注入(DI)
1.1 解决的核心问题
1. 【松耦合】
类只依赖接口,不依赖具体实现
→ 更换实现时不需要修改业务代码
2. 【可测试】
可以注入 Mock 对象进行单元测试
→ 测试时不会真的发邮件、写数据库
3. 【可维护】
依赖关系清晰可见(看构造函数就知道)
→ 便于理解和修改代码
4. 【可扩展】
新增实现只需添加类 + 修改配置
→ 符合开闭原则(对扩展开放,对修改关闭)
5. 【环境切换】
开发/测试/生产环境使用不同实现
→ 只需修改配置,代码不变
1.2 SOLID 原则关联
| 原则 | 全称 | DI 如何支持 |
|---|---|---|
| S | 单一职责 | 类只负责业务逻辑,不负责创建依赖 |
| O | 开闭原则 | 新增实现无需修改现有代码 |
| L | 里氏替换 | 任何实现都可以替换接口 |
| I | 接口隔离 | 依赖小而专注的接口 |
| D | 依赖倒置 | 依赖抽象(接口),不依赖具体实现 |
二、问题演示:没有 DI 的代码
2.1 紧耦合代码示例
// ❌ 不好的设计:紧耦合
public class EmailService
{
public void SendEmail(string to, string message)
{
Console.WriteLine($"发送邮件到 {to}: {message}");
// 实际发送邮件的逻辑...
}
}
public class UserService
{
// 问题1:直接 new 具体类
private EmailService _emailService = new EmailService();
// 问题2:或者在方法内部调用静态方法
public void RegisterUser(string username, string email)
{
// 保存用户...
Console.WriteLine($"用户 {username} 注册成功");
// 发送欢迎邮件
_emailService.SendEmail(email, "欢迎注册!");
}
}
// 使用
class Program
{
static void Main()
{
var userService = new UserService();
userService.RegisterUser("张三", "zhangsan@example.com");
}
}
2.2 这种设计的问题
1. 【无法替换实现】
想换成短信通知?必须修改 UserService 代码
if (type == "email") ... else if (type == "sms") ...
2. 【无法单元测试】
测试 RegisterUser 会真的发邮件!
无法 Mock EmailService
3. 【依赖关系隐藏】
从外部看不出 UserService 依赖什么
需要阅读内部代码才知道
4. 【违反开闭原则】
每次新增通知方式都要修改 UserService
5. 【环境切换困难】
开发环境也会真的发邮件
需要到处写 if (isDevelopment) ...
2.3 静态方法的问题
// ❌ 静态方法方式 - 看似简单,实则问题更多
public static class EmailService
{
public static void SendEmail(string to, string msg)
=> Console.WriteLine($"发送邮件: {to}");
}
public static class SmsService
{
public static void SendSms(string to, string msg)
=> Console.WriteLine($"发送短信: {to}");
}
public class UserService
{
public void RegisterUser(string name, string contact, string type)
{
Console.WriteLine($"用户 {name} 注册成功");
// ❌ 问题:必须知道所有发送方式,每次新增都要改这里
if (type == "email")
EmailService.SendEmail(contact, "欢迎注册");
else if (type == "sms")
SmsService.SendSms(contact, "欢迎注册");
else if (type == "wechat") // 新增
WeChatService.Send(contact, "欢迎注册");
// ... 无穷无尽
}
}
三、解决方案:使用依赖注入
3.1 第一步:定义接口(面向抽象)
// ✅ 定义抽象接口
public interface IMessageService
{
void SendMessage(string to, string message);
}
3.2 第二步:创建多个实现
// ✅ 邮件实现
public class EmailService : IMessageService
{
public void SendMessage(string to, string message)
{
Console.WriteLine($"📧 发送邮件到 {to}: {message}");
}
}
// ✅ 短信实现
public class SmsService : IMessageService
{
public void SendMessage(string to, string message)
{
Console.WriteLine($"📱 发送短信到 {to}: {message}");
}
}
// ✅ 微信实现
public class WeChatService : IMessageService
{
public void SendMessage(string to, string message)
{
Console.WriteLine($"💬 发送微信到 {to}: {message}");
}
}
// ✅ 开发环境:假服务(不真正发送)
public class FakeMessageService : IMessageService
{
public void SendMessage(string to, string message)
{
Console.WriteLine($"🔧 [开发模式] 模拟发送到 {to}: {message}");
}
}
3.3 第三步:通过构造函数注入
// ✅ 好的设计:依赖接口,通过构造函数注入
public class UserService
{
private readonly IMessageService _messageService;
// 构造函数注入:依赖由外部传入
public UserService(IMessageService messageService)
{
_messageService = messageService;
}
public void RegisterUser(string username, string contact)
{
Console.WriteLine($"用户 {username} 注册成功");
// 不关心具体用什么方式发送,只管调用
_messageService.SendMessage(contact, "欢迎注册!");
}
}
3.4 第四步:在组合根配置依赖
// Program.cs - 组合根(Composition Root)
var services = new ServiceCollection();
// 根据环境配置不同实现
if (Environment.IsDevelopment())
{
// 开发环境:使用假服务
services.AddTransient<IMessageService, FakeMessageService>();
}
else
{
// 生产环境:使用真实服务
services.AddTransient<IMessageService, EmailService>();
}
services.AddTransient<UserService>();
var provider = services.BuildServiceProvider();
var userService = provider.GetRequiredService<UserService>();
userService.RegisterUser("张三", "zhangsan@example.com");
四、三种注入方式
4.1 构造函数注入(推荐)
public class OrderService
{
private readonly IPaymentService _payment;
private readonly IInventoryService _inventory;
private readonly IEmailService _email;
// ✅ 构造函数注入 - 最常用,依赖关系一目了然
public OrderService(
IPaymentService payment,
IInventoryService inventory,
IEmailService email)
{
_payment = payment;
_inventory = inventory;
_email = email;
}
public void CreateOrder(Order order)
{
_inventory.Reserve(order.ProductId);
_payment.Process(order.Amount);
_email.SendConfirmation(order);
}
}
优点:
- 依赖关系明确可见
- 对象创建后依赖不可变
- 便于单元测试
- 可以标记为 readonly
4.2 属性注入
public class ReportService
{
// 属性注入 - 适用于可选依赖
public ILogger? Logger { get; set; }
public void GenerateReport()
{
Logger?.Log("开始生成报表...");
// 生成报表逻辑...
Logger?.Log("报表生成完成");
}
}
// 使用
var service = new ReportService();
service.Logger = new ConsoleLogger(); // 可选设置
service.GenerateReport();
优点:
- 灵活,可以不设置
- 适合可选依赖
缺点:
- 依赖可能为空,需要判空
- 依赖关系不够明确
4.3 方法注入
public class DataProcessor
{
// 方法注入 - 每次调用可使用不同实现
public void Process(IDataReader reader)
{
var data = reader.ReadData();
// 处理数据...
}
}
// 使用 - 每次可传入不同实现
var processor = new DataProcessor();
processor.Process(new SqlDataReader()); // SQL 数据源
processor.Process(new FileDataReader()); // 文件数据源
processor.Process(new ApiDataReader()); // API 数据源
优点:
- 每次调用可以使用不同依赖
- 适合策略模式
缺点:
- 每次调用都需要传递
4.4 对比总结
| 方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 构造函数注入 | 必需依赖 | 明确、不可变、易测试 | 参数可能较多 |
| 属性注入 | 可选依赖 | 灵活 | 可能为空、不够明确 |
| 方法注入 | 临时/变化依赖 | 每次可不同 | 每次都需传递 |
五、.NET 内置 DI 容器
5.1 基本使用
using Microsoft.Extensions.DependencyInjection;
// 1. 创建服务容器
var services = new ServiceCollection();
// 2. 注册服务(告诉容器:接口 → 实现)
services.AddTransient<IMessageService, EmailService>();
services.AddTransient<IUserRepository, UserRepository>();
services.AddTransient<UserService>();
// 3. 构建服务提供者
var serviceProvider = services.BuildServiceProvider();
// 4. 获取服务(容器自动注入所有依赖)
var userService = serviceProvider.GetRequiredService<UserService>();
userService.RegisterUser("张三", "test@example.com");
5.2 注册方式
var services = new ServiceCollection();
// 方式1:接口 → 实现
services.AddTransient<IEmailService, EmailService>();
// 方式2:直接注册实现类
services.AddTransient<UserService>();
// 方式3:工厂方法(复杂创建逻辑)
services.AddTransient<IPaymentService>(provider =>
{
var config = provider.GetRequiredService<IConfiguration>();
var paymentType = config["PaymentType"];
return paymentType switch
{
"Alipay" => new AlipayService(),
"WeChat" => new WeChatPayService(),
_ => new DefaultPaymentService()
};
});
// 方式4:注册实例
var logger = new FileLogger("app.log");
services.AddSingleton<ILogger>(logger);
// 方式5:尝试注册(如果已注册则跳过)
services.TryAddTransient<IEmailService, EmailService>();
5.3 获取服务
var provider = services.BuildServiceProvider();
// 方式1:获取必需服务(不存在则抛异常)
var userService = provider.GetRequiredService<UserService>();
// 方式2:获取可选服务(不存在返回 null)
var emailService = provider.GetService<IEmailService>();
// 方式3:获取所有实现(适用于一个接口多个实现)
var allHandlers = provider.GetServices<IEventHandler>();
六、服务生命周期
6.1 三种生命周期
var services = new ServiceCollection();
// 🔵 Transient(瞬时):每次请求都创建新实例
services.AddTransient<ITransientService, TransientService>();
// 🟢 Scoped(作用域):同一作用域内共享实例
services.AddScoped<IScopedService, ScopedService>();
// 🔴 Singleton(单例):整个应用程序共享一个实例
services.AddSingleton<ISingletonService, SingletonService>();
6.2 生命周期图解
【Transient - 瞬时】
请求1 → new A()
请求2 → new A() ← 每次都是新的
请求3 → new A()
适用:轻量级、无状态服务
【Scoped - 作用域】
HTTP请求1 ┬─ 服务A ──┐
└─ 服务B ──┼── 共享同一个实例
│
HTTP请求2 ┬─ 服务A ──┼── 新实例
└─ 服务B ──┘
适用:数据库上下文、工作单元
【Singleton - 单例】
应用启动 ───────────────────────── 应用结束
↓ ↓
创建 销毁
└──── 所有请求共享这一个实例 ─────┘
适用:配置服务、缓存、日志
6.3 生命周期演示
public interface IOperationService
{
Guid OperationId { get; }
}
public class OperationService : IOperationService
{
// 每个实例有唯一 ID
public Guid OperationId { get; } = Guid.NewGuid();
}
// 测试代码
var services = new ServiceCollection();
services.AddTransient<IOperationService, OperationService>();
var provider = services.BuildServiceProvider();
var service1 = provider.GetRequiredService<IOperationService>();
var service2 = provider.GetRequiredService<IOperationService>();
Console.WriteLine($"Service1: {service1.OperationId}");
Console.WriteLine($"Service2: {service2.OperationId}");
Console.WriteLine($"相同实例: {service1.OperationId == service2.OperationId}");
// 输出(Transient):
// Service1: 3f8a1b2c-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// Service2: 7d4e5f6a-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// 相同实例: False
// 如果改成 Singleton,输出会是:
// 相同实例: True
6.4 生命周期选择指南
| 生命周期 | 使用场景 | 示例 |
|---|---|---|
| Transient | 轻量级、无状态服务 | 验证器、工具类 |
| Scoped | 每个请求需要独立状态 | DbContext、工作单元 |
| Singleton | 全局共享、线程安全 | 配置、缓存、日志 |
6.5 注意事项
❌ 不要在 Singleton 中注入 Scoped 或 Transient
// 错误!Singleton 会一直持有第一次注入的 Scoped 服务
public class MySingleton
{
private readonly IScopedService _scoped; // ❌ 危险!
}
解决方案:注入 IServiceProvider,需要时再获取
public class MySingleton
{
private readonly IServiceProvider _provider
public void DoWork()
{
using var scope = _provider.CreateScope();
var scoped = scope.ServiceProvider
.GetRequiredService<IScopedService>()
}
}
七、完整项目示例
7.1 项目结构
UserRegistrationDemo/
├── Models/
│ └── User.cs
├── Services/
│ ├── IUserService.cs # 接口和实现可以放一起
│ ├── UserService.cs
│ ├── IEmailService.cs
│ ├── EmailService.cs
│ └── FakeEmailService.cs
├── Repositories/
│ ├── IUserRepository.cs
│ └── InMemoryUserRepository.cs
├── Controllers/
│ └── UserController.cs
└── Program.cs
7.2 Models
// Models/User.cs
namespace Demo.Models;
public class User
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Email { get; set; } = "";
public string PasswordHash { get; set; } = "";
public DateTime CreatedAt { get; set; } = DateTime.Now;
}
public class RegisterRequest
{
public string Name { get; set; } = "";
public string Email { get; set; } = "";
public string Password { get; set; } = "";
}
public class ServiceResult
{
public bool Success { get; set; }
public string Message { get; set; } = "";
public object? Data { get; set; }
public static ServiceResult Ok(string msg = "成功", object? data = null)
=> new() { Success = true, Message = msg, Data = data };
public static ServiceResult Fail(string msg)
=> new() { Success = false, Message = msg };
}
7.3 Services
// Services/IUserService.cs & UserService.cs
namespace Demo.Services;
public interface IUserService
{
ServiceResult Register(RegisterRequest request);
ServiceResult Login(string email, string password);
User? GetById(int id);
}
public class UserService : IUserService
{
private readonly IUserRepository _repo;
private readonly IEmailService _email;
private readonly ILogger<UserService> _logger;
// 构造函数注入所有依赖
public UserService(
IUserRepository repo,
IEmailService email,
ILogger<UserService> logger)
{
_repo = repo;
_email = email;
_logger = logger;
}
public ServiceResult Register(RegisterRequest request)
{
// 1. 验证
if (string.IsNullOrWhiteSpace(request.Email))
return ServiceResult.Fail("邮箱不能为空");
if (request.Password.Length < 6)
return ServiceResult.Fail("密码至少6位");
// 2. 检查邮箱是否已存在
if (_repo.GetByEmail(request.Email) != null)
return ServiceResult.Fail("邮箱已注册");
// 3. 创建用户
var user = new User
{
Name = request.Name,
Email = request.Email,
PasswordHash = HashPassword(request.Password)
};
_repo.Add(user);
_logger.LogInformation("用户注册成功: {Email}", user.Email);
// 4. 发送欢迎邮件(不关心具体实现)
_ = _email.SendAsync(user.Email, "欢迎", $"你好 {user.Name}!");
return ServiceResult.Ok("注册成功", new { user.Id });
}
public ServiceResult Login(string email, string password)
{
var user = _repo.GetByEmail(email);
if (user == null)
return ServiceResult.Fail("用户不存在");
if (user.PasswordHash != HashPassword(password))
return ServiceResult.Fail("密码错误");
_logger.LogInformation("用户登录: {Email}", email);
return ServiceResult.Ok("登录成功", new { user.Id, user.Name });
}
public User? GetById(int id) => _repo.GetById(id);
private static string HashPassword(string password)
{
using var sha = System.Security.Cryptography.SHA256.Create();
var bytes = sha.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
return Convert.ToBase64String(bytes);
}
}
// Services/IEmailService.cs & 实现
namespace Demo.Services;
public interface IEmailService
{
Task SendAsync(string to, string subject, string body);
}
// 生产环境实现
public class EmailService : IEmailService
{
private readonly ILogger<EmailService> _logger;
public EmailService(ILogger<EmailService> logger) => _logger = logger;
public async Task SendAsync(string to, string subject, string body)
{
_logger.LogInformation("📧 [生产] 发送邮件到 {To}: {Subject}", to, subject);
await Task.Delay(100); // 模拟发送
// 实际项目:调用 SMTP 或第三方邮件服务
}
}
// 开发环境实现(不真正发送)
public class FakeEmailService : IEmailService
{
private readonly ILogger<FakeEmailService> _logger;
public FakeEmailService(ILogger<FakeEmailService> logger) => _logger = logger;
public Task SendAsync(string to, string subject, string body)
{
_logger.LogWarning("📧 [开发] 模拟发送邮件到 {To}: {Subject}", to, subject);
return Task.CompletedTask;
}
}
7.4 Repositories
// Repositories/IUserRepository.cs & 实现
namespace Demo.Repositories;
public interface IUserRepository
{
User? GetById(int id);
User? GetByEmail(string email);
User Add(User user);
IEnumerable<User> GetAll();
}
// 内存实现(开发/测试用)
public class InMemoryUserRepository : IUserRepository
{
private readonly List<User> _users = new();
private int _nextId = 1;
public User? GetById(int id)
=> _users.FirstOrDefault(u => u.Id == id);
public User? GetByEmail(string email)
=> _users.FirstOrDefault(u =>
u.Email.Equals(email, StringComparison.OrdinalIgnoreCase));
public User Add(User user)
{
user.Id = _nextId++;
_users.Add(user);
return user;
}
public IEnumerable<User> GetAll() => _users.ToList();
}
7.5 Controller
// Controllers/UserController.cs
namespace Demo.Controllers;
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
private readonly IUserService _userService;
// 只注入 IUserService,不关心具体实现
public UserController(IUserService userService)
{
_userService = userService;
}
[HttpPost("register")]
public IActionResult Register([FromBody] RegisterRequest request)
{
var result = _userService.Register(request);
return result.Success ? Ok(result) : BadRequest(result);
}
[HttpPost("login")]
public IActionResult Login([FromBody] LoginRequest request)
{
var result = _userService.Login(request.Email, request.Password);
return result.Success ? Ok(result) : Unauthorized(result);
}
[HttpGet("{id}")]
public IActionResult Get(int id)
{
var user = _userService.GetById(id);
return user != null ? Ok(user) : NotFound();
}
}
public record LoginRequest(string Email, string Password);
7.6 Program.cs(组合根 - 核心配置)
// Program.cs
using Demo.Services;
using Demo.Repositories;
var builder = WebApplication.CreateBuilder(args);
//
// 依赖注入配置(核心!)
//
// 1. 注册仓储
if (builder.Environment.IsDevelopment())
{
// 开发环境:内存数据库
builder.Services.AddSingleton<IUserRepository, InMemoryUserRepository>();
}
else
{
// 生产环境:MySQL(示例)
// builder.Services.AddScoped<IUserRepository, MySqlUserRepository>();
builder.Services.AddSingleton<IUserRepository, InMemoryUserRepository>();
}
// 2. 注册邮件服务
if (builder.Environment.IsDevelopment())
{
// 开发环境:假服务,不真正发送
builder.Services.AddTransient<IEmailService, FakeEmailService>();
}
else
{
// 生产环境:真实邮件服务
builder.Services.AddTransient<IEmailService, EmailService>();
}
// 3. 注册业务服务
builder.Services.AddScoped<IUserService, UserService>();
// 4. 添加控制器
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
//
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.MapControllers();
Console.WriteLine($"🚀 环境: {app.Environment.EnvironmentName}");
Console.WriteLine("📍 访问: http://localhost:5000/swagger");
app.Run();
八、单元测试
8.1 使用 Mock 测试
using Moq;
using Xunit;
using Demo.Services;
using Demo.Repositories;
using Demo.Models;
using Microsoft.Extensions.Logging;
public class UserServiceTests
{
private readonly Mock<IUserRepository> _mockRepo;
private readonly Mock<IEmailService> _mockEmail;
private readonly Mock<ILogger<UserService>> _mockLogger;
private readonly UserService _userService;
public UserServiceTests()
{
// 创建 Mock 对象
_mockRepo = new Mock<IUserRepository>();
_mockEmail = new Mock<IEmailService>();
_mockLogger = new Mock<ILogger<UserService>>();
// 注入 Mock 对象创建被测试服务
_userService = new UserService(
_mockRepo.Object,
_mockEmail.Object,
_mockLogger.Object);
}
[Fact]
public void Register_ValidRequest_ShouldSucceed()
{
// Arrange(准备)
var request = new RegisterRequest
{
Name = "张三",
Email = "test@example.com",
Password = "123456"
};
_mockRepo.Setup(r => r.GetByEmail(request.Email))
.Returns((User?)null); // 模拟邮箱不存在
_mockRepo.Setup(r => r.Add(It.IsAny<User>()))
.Returns((User u) => { u.Id = 1; return u; });
// Act(执行)
var result = _userService.Register(request);
// Assert(断言)
Assert.True(result.Success);
Assert.Equal("注册成功", result.Message);
// 验证邮件服务被调用
_mockEmail.Verify(e => e.SendAsync(
request.Email,
It.IsAny<string>(),
It.IsAny<string>()), Times.Once);
}
[Fact]
public void Register_DuplicateEmail_ShouldFail()
{
// Arrange
var request = new RegisterRequest
{
Name = "张三",
Email = "existing@example.com",
Password = "123456"
};
// 模拟邮箱已存在
_mockRepo.Setup(r => r.GetByEmail(request.Email))
.Returns(new User { Email = request.Email });
// Act
var result = _userService.Register(request);
// Assert
Assert.False(result.Success);
Assert.Equal("邮箱已注册", result.Message);
// 验证没有调用 Add
_mockRepo.Verify(r => r.Add(It.IsAny<User>()), Times.Never);
// 验证没有发送邮件
_mockEmail.Verify(e => e.SendAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>()), Times.Never);
}
[Theory]
[InlineData("", "test@test.com", "123456", "邮箱不能为空")]
[InlineData("张三", "test@test.com", "123", "密码至少6位")]
public void Register_InvalidInput_ShouldFail(
string name, string email, string password, string expectedError)
{
var request = new RegisterRequest
{
Name = name,
Email = email,
Password = password
};
var result = _userService.Register(request);
Assert.False(result.Success);
Assert.Contains(expectedError, result.Message);
}
}
九、最佳实践
9.1 DO(推荐做法)
// ✅ 1. 依赖接口,而非实现
public class OrderService
{
private readonly IPaymentService _payment; // ✅ 接口
public OrderService(IPaymentService payment)
{
_payment = payment;
}
}
// ✅ 2. 使用构造函数注入
public class UserService
{
private readonly IRepository _repo;
public UserService(IRepository repo) // ✅ 构造函数注入
{
_repo = repo ?? throw new ArgumentNullException(nameof(repo));
}
}
// ✅ 3. 将依赖标记为 readonly
private readonly IEmailService _email; // ✅ 不可变
// ✅ 4. 在组合根统一配置
// Program.cs
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IEmailService, EmailService>();
// ✅ 5. 使用适当的生命周期
builder.Services.AddSingleton<IConfiguration>(); // 配置
builder.Services.AddScoped<DbContext>(); // 数据库上下文
builder.Services.AddTransient<IValidator>(); // 无状态服务
9.2 DON'T(避免做法)
// ❌ 1. 不要在类内部 new 依赖
public class UserService
{
private EmailService _email = new EmailService(); // ❌ 硬编码
}
// ❌ 2. 不要使用服务定位器模式
public class UserService
{
public void DoWork()
{
// ❌ 隐藏依赖关系
var email = ServiceLocator.Get<IEmailService>();
}
}
// ❌ 3. 不要在 Singleton 中注入 Scoped
public class MySingleton // Singleton
{
private readonly IScopedService _scoped; // ❌ 危险!
}
// ❌ 4. 不要注入太多依赖(超过 3-4 个考虑重构)
public class GodService
{
public GodService(
IService1 s1, IService2 s2, IService3 s3,
IService4 s4, IService5 s5, IService6 s6) // ❌ 太多了
{ }
}
// ❌ 5. 不要在构造函数中做复杂逻辑
public class UserService
{
public UserService(IRepository repo)
{
_repo = repo;
_repo.Initialize(); // ❌ 避免在构造函数中调用方法
LoadData(); // ❌ 避免复杂初始化
}
}
9.3 设计原则
1. 【依赖抽象】
依赖接口,而非具体类
IEmailService, 不是 SmtpEmailService │
2. 【构造函数注入】
必需依赖通过构造函数注入
依赖关系清晰可见
3. 【单一职责】
类只负责自己的业务逻辑
不负责创建依赖
4. 【组合根配置】
所有依赖配置集中在一个地方(Program.cs)
业务代码不关心依赖如何创建
5. 【接口隔离】
接口要小而专注
不要一个接口包含太多方法
十、常见问题解答
Q1: 什么时候不需要依赖注入?
// ✅ 这些情况不需要 DI:
// 1. 纯函数/静态工具方法
Math.Max(1, 2);
string.IsNullOrEmpty(s);
DateTime.Now;
// 2. 简单值对象
var user = new User { Name = "张三" };
// 3. 一次性脚本或小工具
Console.WriteLine("Hello");
// 4. 确定不会变化、不需要测试的简单逻辑
Q2: 接口和实现放一个文件还是分开?
// 小项目:放一起,简洁方便
// Services/UserService.cs
public interface IUserService { }
public class UserService : IUserService { }
// 大项目/多实现:分开放
// Interfaces/IMessageService.cs
// Services/EmailService.cs
// Services/SmsService.cs
// Services/WeChatService.cs
Q3: 如何处理多个实现?
// 方式1:注册所有实现,注入集合
services.AddTransient<INotification, EmailNotification>();
services.AddTransient<INotification, SmsNotification>();
public class NotificationManager
{
private readonly IEnumerable<INotification> _notifications;
public NotificationManager(IEnumerable<INotification> notifications)
{
_notifications = notifications;
}
}
// 方式2:使用键控服务(.NET 8+)
services.AddKeyedTransient<INotification, EmailNotification>("email");
services.AddKeyedTransient<INotification, SmsNotification>("sms");
public class MyService
{
public MyService(
[FromKeyedServices("email")] INotification email,
[FromKeyedServices("sms")] INotification sms)
{ }
}
// 方式3:工厂模式
services.AddTransient<Func<string, INotification>>(provider => key =>
{
return key switch
{
"email" => provider.GetRequiredService<EmailNotification>(),
"sms" => provider.GetRequiredService<SmsNotification>(),
_ => throw new ArgumentException($"Unknown key: {key}")
};
});
Q4: 循环依赖怎么办?
// ❌ 循环依赖
public class A { public A(B b) { } }
public class B { public B(A a) { } }
// ✅ 解决方案1:引入第三个服务
public class A { public A(C c) { } }
public class B { public B(C c) { } }
public class C { } // 共同依赖
// ✅ 解决方案2:使用 Lazy<T>
public class A
{
private readonly Lazy<B> _b;
public A(Lazy<B> b) { _b = b; }
}
// ✅ 解决方案3:方法注入替代构造函数注入
public class A
{
public void DoWork(B b) { } // 需要时才传入
}
Q5: 如何注入配置?
// appsettings.json
{
"EmailSettings": {
"SmtpServer": "smtp.example.com",
"Port": 587
}
}
// 配置类
public class EmailSettings
{
public string SmtpServer { get; set; } = "";
public int Port { get; set; }
}
// 注册
builder.Services.Configure<EmailSettings>(
builder.Configuration.GetSection("EmailSettings"));
// 使用
public class EmailService
{
private readonly EmailSettings _settings;
public EmailService(IOptions<EmailSettings> options)
{
_settings = options.Value;
}
}
十一、总结
11.1 核心要点
【概念】
• IoC:控制权从类内部转移到外部
• DI:通过注入方式提供依赖,是 IoC 的实现方式
【好处】
• 松耦合:依赖接口,不依赖实现
• 可测试:可以注入 Mock 对象
• 可维护:依赖关系清晰
• 可扩展:新增实现不影响现有代码
• 灵活配置:开发/生产环境使用不同实现
【使用方式】
• 定义接口 → 创建实现 → 构造函数注入 → 组合根配置
【生命周期】
• Transient:每次新建
• Scoped:请求内共享
• Singleton:全局共享
11.2 一句话总结
依赖注入的核心不是"不用 new",而是让业务类不知道也不关心具体用的是什么实现,这个决策权交给了外部配置。
11.3 适用场景
| 场景 | 是否使用 DI |
|---|---|
| 学习/Demo/脚本 | 可选 |
| 小型项目 | 推荐 |
| 需要单元测试 | 必须 |
| 需要切换实现 | 必须 |
| 团队协作项目 | 强烈推荐 |
| 中大型项目 | 必须 |
11.4 快速参考
// 1. 定义接口
public interface IService { void DoWork(); }
// 2. 实现接口
public class ServiceA : IService { public void DoWork() { } }
public class ServiceB : IService { public void DoWork() { } }
// 3. 构造函数注入
public class Consumer
{
private readonly IService _service;
public Consumer(IService service) => _service = service;
}
// 4. 注册服务
builder.Services.AddTransient<IService, ServiceA>();
builder.Services.AddScoped<Consumer>();
// 5. 使用
var consumer = provider.GetRequiredService<Consumer>();