.NET 中的高级依赖关系注入模式和服务生命周期管理

科技   2024-11-06 08:00   广东  

作为高级 .NET 开发人员,我们都熟悉基本依赖关系注入 (DI)。然而,细节决定成败,服务生命周期管理不善或误解 DI 模式可能会导致细微的错误、内存泄漏和性能问题。让我们深入研究高级 DI 概念和模式。

了解服务生命周期范围

Singleton Services 的隐患

最常见的陷阱之一是将范围或瞬态服务注入到单一实例中。让我们看看为什么这有问题:

public class SingletonService  
{
private readonly ScopedService \_scopedService;

// 🚫 Anti-pattern: Injecting scoped service into singleton
public SingletonService(ScopedService scopedService)
{
\_scopedService = scopedService;
}
}

为了及早发现这些问题,我们可以创建一个验证帮助程序:

public static class ServiceCollectionValidation  
{
public static void ValidateScopes(this IServiceCollection services)
{
var singletons = services
.Where(s => s.Lifetime == ServiceLifetime.Singleton)
.ToList();

var scopedServices = services
.Where(s => s.Lifetime == ServiceLifetime.Scoped)
.Select(s => s.ServiceType)
.ToList();

foreach (var singleton in singletons)
{
ValidateConstructorInjection(singleton.ImplementationType, scopedServices);
}
}

private static void ValidateConstructorInjection(Type type, List\<Type> scopedServices)
{
var constructors = type.GetConstructors();
foreach (var constructor in constructors)
{
var parameters = constructor.GetParameters();
foreach (var parameter in parameters)
{
if (scopedServices.Contains(parameter.ParameterType))
{
throw new InvalidOperationException(
$"Type {type.Name} is registered as singleton but depends on scoped service {parameter.ParameterType.Name}");
}
}
}
}
}

实现 Factory 模式以实现复杂的生存期管理

当您需要对对象创建和生命周期进行更多控制时:

public interface IServiceFactory<T>  
{
T Create();
}

public class ServiceFactory<T> : IServiceFactory<T>
{
private readonly IServiceProvider _serviceProvider;

public ServiceFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}

public T Create()
{
return ActivatorUtilities.CreateInstance<T>(_serviceProvider);
}
}

// Registration
services.AddSingleton(typeof(IServiceFactory<>), typeof(ServiceFactory<>));

// Usage
public class ComplexService
{
private readonly IServiceFactory<ScopedDependency> _factory;

public ComplexService(IServiceFactory<ScopedDependency> factory)
{
_factory = factory;
}

public void DoWork()
{
using var dependency = _factory.Create();
// Work with the dependency
}
}

高级注册模式

Decorator 模式实现

使用 DI 实现横切关注点的装饰器:

public static class ServiceCollectionExtensions  
{
public static IServiceCollection Decorate<TService, TDecorator>(
this IServiceCollection services)
where TDecorator : TService
{
var wrappedDescriptor = services.FirstOrDefault(
s => s.ServiceType == typeof(TService));

if (wrappedDescriptor == null)
throw new InvalidOperationException($"{typeof(TService).Name} is not registered");

var objectFactory = ActivatorUtilities.CreateFactory(
typeof(TDecorator),
new[] { typeof(TService) });

services.Replace(ServiceDescriptor.Describe(
typeof(TService),
sp => (TService)objectFactory(sp, new[] { sp.CreateInstance(wrappedDescriptor) }),
wrappedDescriptor.Lifetime));

return services;
}

private static object CreateInstance(
this IServiceProvider services,
ServiceDescriptor descriptor)
{
if (descriptor.ImplementationInstance != null)
return descriptor.ImplementationInstance;

if (descriptor.ImplementationFactory != null)
return descriptor.ImplementationFactory(services);

return ActivatorUtilities.GetServiceOrCreateInstance(
services,
descriptor.ImplementationType);
}
}

有条件注册

实施特定于环境的服务注册:

public static class ConditionalRegistration  
{
public static IServiceCollection AddServiceIf<TService, TImplementation>(
this IServiceCollection services,
Func<IServiceProvider, bool> condition,
ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TImplementation : class, TService
where TService : class
{
var descriptor = new ServiceDescriptor(
typeof(TService),
sp => condition(sp)
? ActivatorUtilities.CreateInstance<TImplementation>(sp)
: null,
lifetime);

services.Add(descriptor);
return services;
}
}

// Usage
services.AddServiceIf<IEmailService, SmtpEmailService>(
sp => env.IsDevelopment(),
ServiceLifetime.Singleton);

高级范围界定方案

自定义范围管理

为后台操作创建自定义范围:

public class BackgroundJobScope : IDisposable  
{
private readonly IServiceScope _scope;
private readonly CancellationTokenSource _cts;

public BackgroundJobScope(IServiceProvider serviceProvider)
{
_scope = serviceProvider.CreateScope();
_cts = new CancellationTokenSource();
}

public IServiceProvider ServiceProvider => _scope.ServiceProvider;
public CancellationToken CancellationToken => _cts.Token;

public void Dispose()
{
_cts.Cancel();
_cts.Dispose();
_scope.Dispose();
}
}

// Usage in a background service
public class BackgroundJob : BackgroundService
{
private readonly IServiceProvider _serviceProvider;

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using var jobScope = new BackgroundJobScope(_serviceProvider);
var worker = jobScope.ServiceProvider.GetRequiredService<IWorker>();
await worker.DoWorkAsync(jobScope.CancellationToken);
}
}
}

管理一次性服务

实现自定义处置模式

对于需要特殊清理的服务:

public interface IAsyncDisposableService : IAsyncDisposable  
{
Task InitializeAsync();
}

public class ServiceWithResourceManagement : IAsyncDisposableService
{
private bool _initialized;
private DbConnection _connection;

public async Task InitializeAsync()
{
if (_initialized) return;

_connection = new SqlConnection("connection-string");
await _connection.OpenAsync();
_initialized = true;
}

public async ValueTask DisposeAsync()
{
if (_connection != null)
{
await _connection.DisposeAsync();
}
}
}

// Registration helper
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddAsyncDisposable<TService, TImplementation>(
this IServiceCollection services)
where TService : class
where TImplementation : class, TService, IAsyncDisposableService
{
services.AddScoped<TService>(sp =>
{
var service = ActivatorUtilities.CreateInstance<TImplementation>(sp);
service.InitializeAsync().GetAwaiter().GetResult();
return service;
});

return services;
}
}

最佳实践和建议

  1. 服务生命周期文档

[ServiceLifetime(ServiceLifetime.Scoped)]  
public interface IDocumentedService
{
}

public class ServiceLifetimeAttribute : Attribute
{
public ServiceLifetime Lifetime { get; }
public ServiceLifetimeAttribute(ServiceLifetime lifetime)
{
Lifetime = lifetime;
}
}

2. 启动时的依赖关系验证

public static class StartupExtensions  
{
public static void ValidateServices(this IServiceCollection services)
{
var provider = services.BuildServiceProvider();

foreach (var service in services)
{
try
{
provider.GetService(service.ServiceType);
}
catch (Exception ex)
{
throw new Exception(
$"Error resolving {service.ServiceType.Name}: {ex.Message}");
}
}
}
}

要避免的常见陷阱

  1. 在回调中捕获 Scoped 服务

// 🚫 Anti-pattern  
services.AddSingleton<IHostedService>(sp =>
{
var scopedService = sp.GetRequiredService<IScopedService>();
return new MyHostedService(scopedService); // Wrong!
});

// ✅ Correct pattern
services.AddSingleton<IHostedService>(sp =>
{
var factory = sp.GetRequiredService<IServiceScopeFactory>();
return new MyHostedService(factory);
});

2. 服务定位器反模式

// 🚫 Anti-pattern  
public class ServiceLocator
{
public static IServiceProvider Provider { get; set; }
}

// ✅ Correct pattern: Use constructor injection

正确理解和实现依赖关系注入模式对于构建可维护且可扩展的 .NET 应用程序至关重要。通过遵循这些高级模式和最佳实践,您可以避免常见陷阱并创建更强大的应用程序。

请记得:

  • 始终在启动时验证服务生命周期

  • 使用工厂模式进行复杂的生命周期管理

  • 为资源实施适当的处置模式

  • 避免服务定位器模式

  • 文档服务生命周期

成功实现 DI 的关键不仅在于了解如何使用它,还在于了解不同生命周期范围的影响及其对应用程序行为和性能的影响。


推荐阅读:
.NET 9 预览:C#13 带来的新功能抢先看
在 .NET 中使用强类型 ID 处理实体标识的更好方法
将 .NET Aspire 添加到您现有的 .NET 应用程序中
在 .NET 中使用文件和流(针对 .NET 8 /9 更新)
在 .NET 中使用日期和时间(针对 .NET 8/9 更新)
2款.NET开源且高效的代码格式化工具

点击下方卡片关注DotNet NB

一起交流学习

▲ 点击上方卡片关注DotNet NB,一起交流学习

请在公众号后台

回复 【路线图】获取.NET 2024开发者路线
回复 【原创内容】获取公众号原创内容
回复 【峰会视频】获取.NET Conf大会视频
回复 【个人简介】获取作者个人简介
回复 【年终总结】获取作者年终回顾
回复 加群加入DotNet NB 交流学习群

长按识别下方二维码,或点击阅读原文。和我一起,交流学习,分享心得。

DotNet NB
.NET 技术学习分享,社区热点分享,专注为 .NET 社区做贡献,愿我们互相交流学习,共同推动社区发展
 最新文章