.NET Core 常用的三个生命周期

在 .NET Core 中,生命周期(Lifetime)是依赖注入(DI)容器的重要概念,它决定了对象的创建、使用和销毁时机。在实际应用中,理解和合理利用生命周期对于构建高效、可靠的系统至关重要。本篇文章将详细介绍 .NET Core 常用的三种生命周期——TransientScopedSingleton,并通过实际的案例、场景和实例来帮助读者深入理解这些生命周期的使用方式及其适用场景。

一、依赖注入(DI)概述

依赖注入(Dependency Injection,简称 DI)是一种设计模式,用于将依赖的对象传递给需要它的类,而不是由类自己创建依赖对象。在 .NET Core 中,依赖注入是内置功能,可以通过 Startup.cs 中的 ConfigureServices 方法来配置。

DI 容器使用生命周期来管理对象的实例。不同的生命周期控制对象的创建和销毁方式,确保对象的创建和销毁符合应用程序的需求。

二、生命周期类型

.NET Core 中有三种常用的生命周期:TransientScopedSingleton。接下来,我们将分别介绍这三种生命周期。

1. Transient 生命周期

定义

Transient 生命周期的对象每次被请求时都会重新创建。也就是说,当一个服务被请求时,依赖注入容器会为每个请求创建一个新的实例。

适用场景

  • 轻量级、无状态的服务:如果一个服务是无状态的且不需要跨多个请求共享数据,那么使用 Transient 生命周期是一个很好的选择。例如,简单的计算服务、日志记录服务等。
  • 每次请求都需要不同实例的场景:例如,在一些短暂的操作中,需要确保每个请求都获得独立的实例。

示例

假设我们有一个简单的服务 IUserService,它返回当前用户的 ID。我们希望每次注入 IUserService 时都创建一个新的实例。

csharpCopy Code
// 定义 IUserService 接口 public interface IUserService { Guid GetCurrentUserId(); } // 实现 IUserService 接口 public class UserService : IUserService { private readonly Guid _userId; public UserService() { // 每个实例都应该有不同的用户 ID _userId = Guid.NewGuid(); } public Guid GetCurrentUserId() { return _userId; } } // Startup.cs 中注册服务 public void ConfigureServices(IServiceCollection services) { services.AddTransient<IUserService, UserService>(); }

使用案例

csharpCopy Code
public class MyController : ControllerBase { private readonly IUserService _userService; public MyController(IUserService userService) { _userService = userService; } [HttpGet] public IActionResult GetUser() { return Ok(_userService.GetCurrentUserId()); } }

在这个例子中,IUserService 被注册为 Transient 生命周期。每次请求都会创建一个新的 UserService 实例,且每个实例的 UserId 都不相同。

2. Scoped 生命周期

定义

Scoped 生命周期的对象在每个请求(或每个作用域)内是唯一的。当服务被注册为 Scoped 时,每个请求(HTTP 请求,或者是由开发者显式创建的作用域)都会使用同一个实例,直到请求结束。请求结束后,实例被销毁。

适用场景

  • 跨多个组件共享的服务:当需要跨多个组件共享一个服务的状态,但该状态仅在一次请求的生命周期内有效时,使用 Scoped 生命周期是一个好选择。
  • 数据库上下文:例如,使用 Entity Framework Core 访问数据库时,通常会将 DbContext 注册为 Scoped,确保每个请求有一个唯一的数据库上下文实例。

示例

假设我们有一个 OrderService,它需要在每个 HTTP 请求中共享一个数据库上下文(例如 DbContext)实例。我们可以将它注册为 Scoped

csharpCopy Code
// 定义 OrderService 类 public class OrderService { private readonly ApplicationDbContext _context; public OrderService(ApplicationDbContext context) { _context = context; } public IEnumerable<Order> GetOrders() { return _context.Orders.ToList(); } } // Startup.cs 中注册服务 public void ConfigureServices(IServiceCollection services) { services.AddScoped<OrderService>(); services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); }

使用案例

csharpCopy Code
public class OrderController : ControllerBase { private readonly OrderService _orderService; public OrderController(OrderService orderService) { _orderService = orderService; } [HttpGet] public IActionResult GetOrders() { return Ok(_orderService.GetOrders()); } }

在这个例子中,OrderService 被注册为 Scoped 生命周期。在每个 HTTP 请求中,OrderServiceApplicationDbContext 会共享同一个实例,但在不同的请求之间,它们是不同的实例。

3. Singleton 生命周期

定义

Singleton 生命周期的对象在整个应用程序生命周期内只会被创建一次。无论有多少个请求或依赖注入容器被创建,都会复用同一个实例。该实例在第一次创建时被初始化,并且在应用程序的整个生命周期内保持存在,直到应用程序停止。

适用场景

  • 需要全局共享的数据或状态:例如,缓存服务、配置管理、日志记录器等。
  • 只需创建一次的资源:例如,连接池、静态数据等。

示例

假设我们有一个 CacheService,它用于在应用程序中共享一些缓存数据。我们希望在整个应用程序生命周期内只创建一个 CacheService 实例。

csharpCopy Code
// 定义 CacheService 类 public class CacheService { private readonly Dictionary<string, string> _cache = new(); public string Get(string key) { _cache.TryGetValue(key, out var value); return value; } public void Set(string key, string value) { _cache[key] = value; } } // Startup.cs 中注册服务 public void ConfigureServices(IServiceCollection services) { services.AddSingleton<CacheService>(); }

使用案例

csharpCopy Code
public class CacheController : ControllerBase { private readonly CacheService _cacheService; public CacheController(CacheService cacheService) { _cacheService = cacheService; } [HttpGet("{key}")] public IActionResult GetCache(string key) { var value = _cacheService.Get(key); if (value == null) { return NotFound(); } return Ok(value); } [HttpPost] public IActionResult SetCache(string key, string value) { _cacheService.Set(key, value); return Ok(); } }

在这个例子中,CacheService 被注册为 Singleton 生命周期。这意味着,在应用程序的整个生命周期内,所有的请求都将共享同一个 CacheService 实例。

三、生命周期的选择和设计

选择正确的生命周期是构建健壮、易于维护应用程序的关键。在选择生命周期时,可以考虑以下几个方面:

  • 无状态的服务:如果服务不依赖于外部状态,或者服务自身不维护状态,那么它应该使用 Transient 生命周期。
  • 跨多个请求共享数据的服务:如果服务需要在同一请求中跨多个组件共享数据,但每个请求的实例不需要与其他请求共享,则应使用 Scoped 生命周期。
  • 全局共享的服务:如果服务需要在应用程序的整个生命周期内保持共享状态,那么它应该使用 Singleton 生命周期。

四、总结

.NET Core 提供的三种生命周期——TransientScopedSingleton,各有其独特的适用场景和设计原则。合理选择生命周期,可以帮助我们优化应用程序的性能、可维护性以及扩展性。

  • Transient:适用于短生命周期、无状态的服务,每次请求都创建新的实例。
  • Scoped:适用于跨多个组件共享但仅在请求范围内有效的服务,确保每个请求有唯一实例。
  • Singleton:适用于全局共享的服务,整个应用程序生命周期内只创建一次实例。

在实际应用中,理解这些生命周期的特点,并根据具体的需求选择合适的生命周期,是构建高效可靠的 .NET Core 应用程序的关键。