AddTransient、AddScoped和AddSingleton服务差异解析
技术背景
在 .NET 的依赖注入(Dependency Injection,简称 DI)系统中,服务的生命周期管理是一个核心概念。合理地选择服务的生命周期,能够显著影响应用程序的性能、资源使用效率以及线程安全性。AddTransient
、AddScoped
和 AddSingleton
是 .NET 中用于注册服务并指定其生命周期的方法,它们分别代表了三种不同的服务生命周期模式。
实现步骤
定义接口和实现类
首先,我们需要定义一组接口和对应的实现类,用于演示不同生命周期的服务。
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 40 41 42 43 44 45 46
| using System;
namespace DependencyInjectionSample.Interfaces { public interface IOperation { Guid OperationId { get; } }
public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }
public interface IOperationSingletonInstance : IOperation { } }
using System; using DependencyInjectionSample.Interfaces; namespace DependencyInjectionSample.Classes { public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton, IOperationSingletonInstance { Guid _guid; public Operation() : this(Guid.NewGuid()) {
}
public Operation(Guid guid) { _guid = guid; }
public Guid OperationId => _guid; } }
|
注册服务
在 ConfigureServices
方法中,使用 AddTransient
、AddScoped
和 AddSingleton
方法注册不同生命周期的服务。
1 2 3 4 5
| services.AddTransient<IOperationTransient, Operation>(); services.AddScoped<IOperationScoped, Operation>(); services.AddSingleton<IOperationSingleton, Operation>(); services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty)); services.AddTransient<OperationService, OperationService>();
|
创建服务类
创建一个 OperationService
类,该类依赖于不同生命周期的 Operation
类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| using DependencyInjectionSample.Interfaces;
namespace DependencyInjectionSample.Services { public class OperationService { public IOperationTransient TransientOperation { get; } public IOperationScoped ScopedOperation { get; } public IOperationSingleton SingletonOperation { get; } public IOperationSingletonInstance SingletonInstanceOperation { get; }
public OperationService(IOperationTransient transientOperation, IOperationScoped scopedOperation, IOperationSingleton singletonOperation, IOperationSingletonInstance instanceOperation) { TransientOperation = transientOperation; ScopedOperation = scopedOperation; SingletonOperation = singletonOperation; SingletonInstanceOperation = instanceOperation; } } }
|
创建控制器
创建一个 OperationsController
控制器,用于演示不同生命周期的服务在请求中的表现。
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 40 41
| using DependencyInjectionSample.Interfaces; using DependencyInjectionSample.Services; using Microsoft.AspNetCore.Mvc;
namespace DependencyInjectionSample.Controllers { public class OperationsController : Controller { private readonly OperationService _operationService; private readonly IOperationTransient _transientOperation; private readonly IOperationScoped _scopedOperation; private readonly IOperationSingleton _singletonOperation; private readonly IOperationSingletonInstance _singletonInstanceOperation;
public OperationsController(OperationService operationService, IOperationTransient transientOperation, IOperationScoped scopedOperation, IOperationSingleton singletonOperation, IOperationSingletonInstance singletonInstanceOperation) { _operationService = operationService; _transientOperation = transientOperation; _scopedOperation = scopedOperation; _singletonOperation = singletonOperation; _singletonInstanceOperation = singletonInstanceOperation; }
public IActionResult Index() { ViewBag.Transient = _transientOperation; ViewBag.Scoped = _scopedOperation; ViewBag.Singleton = _singletonOperation; ViewBag.SingletonInstance = _singletonInstanceOperation;
ViewBag.Service = _operationService; return View(); } } }
|
核心代码
AddTransient
1
| services.AddTransient<IOperationTransient, Operation>();
|
每次请求 IOperationTransient
服务时,都会创建一个新的 Operation
实例。
AddScoped
1
| services.AddScoped<IOperationScoped, Operation>();
|
在同一个请求范围内,每次请求 IOperationScoped
服务时,都会返回同一个 Operation
实例;不同请求范围则会创建新的实例。
AddSingleton
1
| services.AddSingleton<IOperationSingleton, Operation>();
|
在整个应用程序生命周期内,只会创建一个 Operation
实例,所有请求都会使用该实例。
最佳实践
- Transient:适用于轻量级、无状态的服务,因为每次请求都会创建新实例,可能会消耗更多的内存和资源。例如,简单的工具类服务。
- Scoped:当需要在一个请求范围内保持状态时,使用 Scoped 生命周期是一个不错的选择。例如,在 ASP.NET Core 中,
DbContext
默认使用 Scoped 生命周期,确保在同一个请求中使用同一个数据库上下文。 - Singleton:对于需要在整个应用程序中共享状态的服务,如配置服务、日志服务、缓存服务等,使用 Singleton 生命周期可以提高性能和资源利用率。但需要注意线程安全问题。
常见问题
避免依赖倒置问题
一个服务不应该依赖于生命周期比自己短的服务,否则可能会导致“捕获依赖”(Captive Dependencies)问题。例如,Singleton 服务不应该依赖于 Transient 或 Scoped 服务,因为这会导致 Transient 或 Scoped 服务的实例被意外地保留在整个应用程序生命周期内,可能会引发线程安全问题和内存泄漏。
线程安全问题
Singleton 服务由于在整个应用程序生命周期内共享同一个实例,需要特别注意线程安全问题。如果服务包含可变状态,需要使用适当的锁机制来避免并发访问问题。
内存泄漏问题
Singleton 服务如果持有大量内存,并且在应用程序运行期间内存使用不断增长,可能会导致内存泄漏。因此,对于高内存需求但使用频率较低的服务,不建议使用 Singleton 生命周期。