C# 高级教程 - 16 依赖注入

依赖注入(DI)是现代 .NET 应用架构的核心模式。ASP.NET Core 内置了完整的 DI 容器,了解其工作原理、生命周期管理、高级注册方式和常见陷阱,对于构建可测试、可维护的企业级应用至关重要。本文将深入探讨 DI 容器的内部机制和最佳实践。

DI 基础回顾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 没有 DI —— 紧耦合
public class OrderService
{
private readonly SqlOrderRepository _repo = new(); // 硬编码依赖
private readonly EmailService _email = new();
}

// 有 DI —— 松耦合
public class OrderService
{
private readonly IOrderRepository _repo;
private readonly INotificationService _notification;

public OrderService(
IOrderRepository repo,
INotificationService notification)
{
_repo = repo;
_notification = notification;
}
}

生命周期管理

1
2
3
4
// 注册
services.AddSingleton<IAppCache, MemoryCache>(); // 单例:整个进程一个实例
services.AddScoped<IUnitOfWork, UnitOfWork>(); // 作用域:每个 HTTP 请求一个实例
services.AddTransient<IEmailService, EmailService>(); // 瞬时:每次注入都创建新实例

生命周期选择指南

生命周期 适用场景 线程安全要求
Singleton 配置、缓存、日志、无状态服务 ✅ 必须线程安全
Scoped EF Core DbContext、Unit of Work ❌ 不同请求独立
Transient 轻量级无状态服务、工具类 视情况

生命周期陷阱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 陷阱 1:Singleton 依赖 Scoped —— 导致 Scoped 退化为 Singleton
services.AddSingleton<IService, Service>();
services.AddScoped<IRepository, Repository>(); // 实际上变成了 Singleton!

// 解决:使用 IServiceScopeFactory
public class SingletonService : IService
{
private readonly IServiceScopeFactory _scopeFactory;

public SingletonService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}

public async Task ProcessAsync()
{
using var scope = _scopeFactory.CreateScope();
var repo = scope.ServiceProvider.GetRequiredService<IRepository>();
await repo.SaveAsync();
}
}

高级注册技巧

条件注册

1
2
3
4
5
6
7
8
9
10
11
// 环境条件
if (builder.Environment.IsDevelopment())
services.AddScoped<IEmailService, FakeEmailService>();
else
services.AddScoped<IEmailService, SmtpEmailService>();

// 基于配置条件
if (configuration.GetValue<bool>("Features:UseRedisCache"))
services.AddSingleton<ICacheService, RedisCacheService>();
else
services.AddSingleton<ICacheService, MemoryCacheService>();

工厂注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 带参数的工厂方法
services.AddScoped<IConnection>(sp =>
{
var config = sp.GetRequiredService<IConfiguration>();
var connectionString = config.GetConnectionString("Default");
return new SqlConnection(connectionString);
});

// 命名注册 —— 根据 Key 选择实现
services.AddKeyedScoped<ICacheService, RedisCacheService>("redis");
services.AddKeyedScoped<ICacheService, MemoryCacheService>("memory");

public class CacheConsumer
{
public CacheConsumer(
[FromKeyedServices("redis")] ICacheService redisCache)
{
}
}

装饰器模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 装饰已有服务
services.AddScoped<IUserService, UserService>();
services.Decorate<IUserService, LoggingUserService>();
services.Decorate<IUserService, CachingUserService>();

public class LoggingUserService : IUserService
{
private readonly IUserService _inner;
private readonly ILogger _logger;

public LoggingUserService(IUserService inner, ILogger<LoggingUserService> logger)
{
_inner = inner;
_logger = logger;
}

public async Task<User> GetUserAsync(int id)
{
_logger.LogInformation("获取用户 {Id}", id);
return await _inner.GetUserAsync(id);
}
}

批量注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 扫描程序集注册所有 IHandler 实现
var handlerType = typeof(IHandler<>);
services.Scan(scan => scan
.FromAssembliesOf<IHandler>()
.AddClasses(classes => classes.AssignableTo(handlerType))
.AsImplementedInterfaces()
.WithScopedLifetime());

// 或者手动批量
var handlerTypes = Assembly.GetExecutingAssembly()
.GetTypes()
.Where(t => t.IsClass && !t.IsAbstract &&
t.GetInterfaces().Any(i =>
i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IHandler<>)));

foreach (var type in handlerTypes)
{
foreach (var iface in type.GetInterfaces()
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IHandler<>)))
{
services.AddScoped(iface, type);
}
}

使用第三方容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 安装: dotnet add package Autofac.Extensions.DependencyInjection

builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());

builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder =>
{
containerBuilder.RegisterModule<MyAutofacModule>();
});

public class MyAutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<UserService>()
.As<IUserService>()
.InstancePerLifetimeScope();

// 属性注入(Autofac 特性)
builder.RegisterType<OrderService>()
.PropertiesAutowired();

// 拦截器(AOP)
builder.RegisterType<LoggingInterceptor>();
builder.RegisterType<CachedService>()
.InterceptedBy(typeof(LoggingInterceptor));
}
}

选项模式(Options Pattern)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 1. 定义配置类
public class SmtpOptions
{
public const string SectionName = "Smtp";

public string Host { get; set; } = "";
public int Port { get; set; } = 587;
public string Username { get; set; } = "";
public string Password { get; set; } = "";
}

// 2. 注册
services.Configure<SmtpOptions>(configuration.GetSection(SmtpOptions.SectionName));

// 3. 注入使用
public class EmailService
{
private readonly SmtpOptions _options;
private readonly IOptionsSnapshot<SmtpOptions> _optionsSnapshot;

public EmailService(IOptions<SmtpOptions> options)
{
_options = options.Value; // Singleton 安全
}

public EmailService(IOptionsSnapshot<SmtpOptions> snapshot)
{
_optionsSnapshot = snapshot; // Scoped,每次请求重新读取
}

// IOptionsMonitor 支持配置热更新
public EmailService(IOptionsMonitor<SmtpOptions> monitor)
{
monitor.OnChange(newOptions =>
{
Console.WriteLine($"配置已更新: {newOptions.Host}");
});
}
}

验证注入配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 启动时验证 DI 注册(开发环境)
if (builder.Environment.IsDevelopment())
{
var sp = services.BuildServiceProvider();
using var scope = sp.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<IUserService>();
// 显式解析验证所有服务可正确创建
}

// .NET 6+ 内置验证
builder.Host.UseDefaultServiceProvider(options =>
{
options.ValidateOnBuild = true; // 启动时验证
options.ValidateScopes = true; // 验证 Singleton
});

ASP.NET Core 中的实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 典型的三层架构 DI
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IOrderService, OrderService>();

// 中间件的依赖注入
public class CustomMiddleware
{
private readonly RequestDelegate _next;

public CustomMiddleware(RequestDelegate next) => _next = next;

public async Task InvokeAsync(HttpContext context, IUserService userService)
{
// userService 来自 DI 作用域
await _next(context);
}
}

DI 常见反模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 反模式 1:服务定位器(Service Locator)
public class BadService
{
public void DoWork()
{
var repo = ServiceLocator.Current.GetInstance<IRepository>(); // ❌
}
}

// 反模式 2:静态注入
public static class StaticConfig
{
public static IServiceProvider? ServiceProvider { get; set; } // ❌
}

// 反模式 3:注入过多依赖(上帝构造函数)
public class GodService
{
public GodService(
IService1 s1, IService2 s2, IService3 s3, // 6+ 个参数
IService4 s4, IService5 s5, IService6 s6) { } // ❌
// 重构:拆分为更小的服务
}

// 反模式 4:在 Singleton 中注入 Scoped
public class EvilSingleton
{
public EvilSingleton(IUserRepository repo) { } // ❌ Scope 冲突
}

总结:依赖注入是实现控制反转(IoC)的核心工具。正确理解 Singleton/Scoped/Transient 的生命周期差异、掌握条件注册和工厂注册的灵活性、以及避免生命周期冲突和过度注入,是构建可维护 .NET 应用的基础。ASP.NET Core 内置 DI 容器已足够强大,只有在需要 AOP、属性注入等高级特性时才考虑第三方容器。

ByteFisher
分享编程技术 · 记录钓鱼乐趣
扫码关注
▸ 扫码关注 ◂
分享: