依赖关系注入 (DI) 在现代 .NET 开发中已成为必不可少的,它实现了松散耦合、可测试性和灵活性。但是,了解服务生命周期如何影响内存和资源连接至关重要。服务生命周期管理不善可能会导致内存泄漏、套接字耗尽和应用程序性能不佳等问题。
在本文中,我们将探讨内存和连接在 DI 中的作用,重点关注:
每个服务生命周期(、 和 )对内存和连接的影响不同。让我们用实际示例和最佳实践来分解每个生命周期。AddScopedAddTransientAddSingleton
AddScoped每个 HTTP 请求创建一次服务,并在请求结束时删除服务。这使得它非常适合数据库上下文 () 等服务,其中每个请求都需要自己的一组跟踪更改和单独的数据库连接。DbContext
在此示例中,我们有一个访问数据库的 a。该 已注册为 scoped,这意味着它将为每个请求创建一次,并在请求完成时被释放。ProductServiceAppDbContext
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Register DbContext with Scoped lifetime (per request)
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
// Register repositories and Unit of Work with Scoped lifetime
services.AddScoped<IProductRepository, ProductRepository>();
services.AddScoped<IUnitOfWork, UnitOfWork>();
// Register ProductService with Scoped lifetime
services.AddScoped<IProductService, ProductService>();
}
}
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
} public DbSet<Product> Products { get; set; }
}
public interface IProductService
{
Task<IEnumerable<Product>> GetTopProductsAsync(int count);
Task AddProductAsync(Product product);
Task UpdateProductAsync(Product product);
Task DeleteProductAsync(int productId);
}
public class ProductService : IProductService
{
private readonly IUnitOfWork _unitOfWork;
public ProductService(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public async Task<IEnumerable<Product>> GetTopProductsAsync(int count)
{
return await _unitOfWork.Products.GetTopProductsAsync(count);
}
public async Task AddProductAsync(Product product)
{
await _unitOfWork.Products.AddAsync(product);
await _unitOfWork.CompleteAsync(); // Commit changes
}
public async Task UpdateProductAsync(Product product)
{
_unitOfWork.Products.Update(product);
await _unitOfWork.CompleteAsync(); // Commit changes
}
public async Task DeleteProductAsync(int productId)
{
var product = await _unitOfWork.Products.GetByIdAsync(productId);
if (product != null)
{
_unitOfWork.Products.Delete(product);
await _unitOfWork.CompleteAsync(); // Commit changes
}
}
}
AddTransient每次请求服务时都会创建服务。这使得它们非常适合无状态的轻量级操作,例如在方法调用之间不保存任何状态的实用程序服务。
在此示例中,该服务是瞬态的,因为每个请求都独立处理图像,并且不需要调用之间的状态持久性。ImageOptimizer
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Register ImageOptimizer with Transient lifetime
services.AddTransient<IImageOptimizer, ImageOptimizer>();
}
}
public interface IImageOptimizer
{
byte[] OptimizeImage(byte[] imageData);
}
public class ImageOptimizer : IImageOptimizer
{
public byte[] OptimizeImage(byte[] imageData)
{
// Simulate image optimization logic
// Compress or resize the image
return CompressImage(imageData);
}
private byte[] CompressImage(byte[] imageData)
{
// Mock compression logic
return imageData;
}
}
AddSingleton服务创建一次,并在整个应用程序中共享。单一实例服务最适合用于创建成本高昂且不需要经常更改的对象,例如 HTTP 客户端、缓存服务或配置提供程序。
在此示例中, 注册为 ,但它不是创建和管理自己的 ,而是用于创建正确管理的实例。WeatherServiceSingletonHttpClientIHttpClientFactoryHttpClient
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Register HttpClientFactory to manage HttpClient instances
services.AddHttpClient();
// Register WeatherService as Singleton
services.AddSingleton<IWeatherService, WeatherService>();
}
}
public interface IWeatherService
{
Task<WeatherForecast> GetWeatherAsync(string city);
}
public class WeatherService : IWeatherService
{
private readonly IHttpClientFactory _httpClientFactory;
public WeatherService(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public async Task<WeatherForecast> GetWeatherAsync(string city)
{
var client = _httpClientFactory.CreateClient();
var response = await client.GetStringAsync($"https://api.weather.com/v3/wx/forecast?city={city}");
return JsonConvert.DeserializeObject<WeatherForecast>(response);
}
}
对于创建成本高昂且可以在多个请求中安全重用的服务,仍然是一个不错的选择。以下是一些替代示例:Singleton
public class MemoryCacheService : ICacheService
{
private readonly IMemoryCache _memoryCache;
public MemoryCacheService(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
public void SetCache<T>(string key, T item, TimeSpan duration)
{
_memoryCache.Set(key, item, duration);
}
public T GetCache<T>(string key)
{
_memoryCache.TryGetValue(key, out T value);
return value;
}
}
// Startup.cs
services.AddSingleton<ICacheService, MemoryCacheService>();
2. 配置提供程序:
public class ConfigurationProvider : IConfigurationProvider
{
private readonly IConfiguration _configuration;
public ConfigurationProvider(IConfiguration configuration)
{
_configuration = configuration;
}
public string GetSetting(string key)
{
return _configuration[key];
}
}
// Startup.cs
services.AddSingleton<IConfigurationProvider, ConfigurationProvider>();
关键要点:
在 .NET 中,服务注册的顺序通常无关紧要,除非在某些情况下重写服务或使用条件逻辑。以下是需要考虑的关键因素:
如果多次注册同一服务,则上次注册将覆盖前一次注册。这对于将默认实现替换为自定义实现非常有用。
services.AddSingleton<IExampleService, DefaultExampleService>();
services.AddSingleton<IExampleService, CustomExampleService>(); // CustomExampleService will be used
不同服务生存期 (、 和 ) 的注册顺序不会影响其行为。服务的生命周期取决于它的注册方式,而不是顺序。ScopedTransientSingleton
services.AddSingleton<IExampleService, ExampleServiceSingleton>();
services.AddScoped<IAnotherService, AnotherServiceScoped>();
services.AddTransient<IOtherService, OtherServiceTransient>();
每个服务都根据其指定的生命周期运行,而不管注册顺序如何。
当有条件地注册服务时(例如,基于环境),顺序很重要。满足条件的注册将优先。
if (env.IsDevelopment())
{
services.AddScoped<IExampleService, DevelopmentExampleService>();
}
else
{
services.AddScoped<IExampleService, ProductionExampleService>();
}
虽然服务注册的顺序通常无关紧要,但方法中的中间件顺序很重要,因为中间件是按照添加的顺序执行的。Startup.Configure
app.UseAuthentication(); // Must come before UseAuthorization
app.UseAuthorization();
在 .NET 中有效使用依赖项注入需要的不仅仅是选择服务生存期。通过仔细管理内存和连接,可以防止内存泄漏和资源耗尽等常见问题。以下是关键要点:
通过遵循这些最佳实践,您的 .NET 应用程序将针对内存管理和高效连接使用进行优化,从而确保更好的可扩展性和性能。