DEV Community

DD
DD

Posted on

NetCore中Singleton、Transient、Scoped举例分析三种模式的区别

在 ASP.NET Core 的依赖注入(Dependency Injection, DI)容器中,Singleton、Transient 和 Scoped 是三种核心生命周期管理方式,用来控制服务实例的创建与共享规则。

理解这三种模式,是掌握 ASP.NET Core 架构设计的关键基础。

一、代码示例

下面用一个简单的示例来说明它们之间的区别:

1. 控制器调用

    [ApiController]
    [Route("api/[controller]")]
    public class TestController : ControllerBase
    {
        private readonly IServiceProvider _serviceProvider;

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

        [HttpGet]
        public IActionResult Get()
        {
            Console.WriteLine("======= 收到 API 请求 =======");
            var my1 = _serviceProvider.GetService<IMyService>();
            var my2 = _serviceProvider.GetService<IMyService>();

            my1.DoSomething();
            my2.DoSomething();

            Console.WriteLine("======= 结束 API 请求 =======\n\n");

            return Ok();
        }
    }
Enter fullscreen mode Exit fullscreen mode

2. 服务定义

    public interface IMyService
    {
        void DoSomething();
    }

    public class MyService : IMyService
    {
        private readonly string _instanceId;

        public MyService()
        {
            _instanceId = Random.Shared.Next(100, 999).ToString();
            Console.WriteLine($"[MyService-构造函数] 实例 Id 为: {_instanceId}");
        }

        public void DoSomething()
        {
            Console.WriteLine($"[MyService-方法调用] 实例 Id 为: {_instanceId}");
        }
    }
Enter fullscreen mode Exit fullscreen mode

3. 注册方式

// 使用 Singleton 生命周期的示例
builder.Services.AddSingleton<IMyService, MyService>();

// 使用 Scoped 生命周期的示例
builder.Services.AddScoped<IMyService, MyService>();

// 使用 Transient 生命周期的示例
builder.Services.AddTransient<IMyService, MyService>();
Enter fullscreen mode Exit fullscreen mode

二、运行结果

现在,我们来看看它们在不同的场景中的行为:

1. Singleton(单例)

//**********第一次调用API**********
======= 收到 API 请求 =======
[MyService-构造函数] 实例 Id : 178
[MyService-方法调用] 实例 Id : 178
[MyService-方法调用] 实例 Id : 178
======= 结束 API 请求 =======

//**********第二次调用API**********
======= 收到 API 请求 =======
[MyService-方法调用] 实例 Id : 178
[MyService-方法调用] 实例 Id : 178
======= 结束 API 请求 =======

//**********第三次调用API**********
======= 收到 API 请求 =======
[MyService-方法调用] 实例 Id : 178
[MyService-方法调用] 实例 Id : 178
======= 结束 API 请求 =======
Enter fullscreen mode Exit fullscreen mode
  • 全局只创建一个实例
  • 整个应用程序生命周期内共享
  • 所有请求都使用同一个对象

👉 特点:全局唯一

2. Scoped (作用域)

//**********第一次调用API**********
======= 收到 API 请求 =======
[MyService-构造函数] 实例 Id : 438
[MyService-方法调用] 实例 Id : 438
[MyService-方法调用] 实例 Id : 438
======= 结束 API 请求 =======

//**********第二次调用API**********
======= 收到 API 请求 =======
[MyService-构造函数] 实例 Id : 674
[MyService-方法调用] 实例 Id : 674
[MyService-方法调用] 实例 Id : 674
======= 结束 API 请求 =======

//**********第三次调用API**********
======= 收到 API 请求 =======
[MyService-构造函数] 实例 Id : 365
[MyService-方法调用] 实例 Id : 365
[MyService-方法调用] 实例 Id : 365
======= 结束 API 请求 =======
Enter fullscreen mode Exit fullscreen mode
  • 同一个请求(Scope)内共享一个实例
  • 不同请求之间是不同实例
  • 常见于 Web API 请求生命周期

👉 特点:请求内单例,请求间隔离

3.Transient(瞬态)

//**********第一次调用API**********
======= 收到 API 请求 =======
[MyService-构造函数] 实例 Id : 794
[MyService-方法调用] 实例 Id : 794
[MyService-构造函数] 实例 Id : 182
[MyService-方法调用] 实例 Id : 182
======= 结束 API 请求 =======

//**********第二次调用API**********
======= 收到 API 请求 =======
[MyService-构造函数] 实例 Id : 436
[MyService-方法调用] 实例 Id : 436
[MyService-构造函数] 实例 Id : 588
[MyService-方法调用] 实例 Id : 588
======= 结束 API 请求 =======

//**********第三次调用API**********
======= 收到 API 请求 =======
[MyService-构造函数] 实例 Id : 391
[MyService-方法调用] 实例 Id : 391
[MyService-构造函数] 实例 Id : 182
[MyService-方法调用] 实例 Id : 182
======= 结束 API 请求 =======
Enter fullscreen mode Exit fullscreen mode
  • 每次请求都会创建一个新实例
  • 每次解析(Resolve)都会生成新的对象
  • 不共享状态

👉 特点:用完即弃,每次都是新的

三、区别总结

生命周期 创建次数 是否共享 适用场景
Singleton 全局一次 全局共享 配置、缓存、日志
Scoped 每个请求一次 请求内共享 DB上下文、业务服务
Transient 每次解析一次 不共享 无状态工具类

四、使用建议

✔ 推荐使用方式

  • 默认优先:Scoped
  • 无状态工具:Transient
  • 全局共享且线程安全:Singleton

⚠️ 常见坑

  • ❌ 在 Singleton 中注入 Scoped(容易导致作用域失效)
  • ❌ Singleton 持有可变状态(线程安全风险)
  • ❌ 过度使用 Transient(性能浪费)

五、总结理解

  • Singleton:整个应用只有一个
  • Scoped:每个请求一个
  • Transient:每次用都是新的

Top comments (0)