【问题标题】:AddTransient, AddScoped and AddSingleton Services DifferencesAddTransient、AddScoped 和 AddSingleton 服务的区别
【发布时间】:2016-11-03 10:25:18
【问题描述】:

我想在 ASP.NET Core 中实现 dependency injection (DI)。因此,将此代码添加到 ConfigureServices 方法后,两种方式都可以工作。

ASP.NET Core 中的services.AddTransientservice.AddScoped 方法有什么区别?

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.

    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddScoped<IEmailSender, AuthMessageSender>();
}

【问题讨论】:

  • @tmg 文档说“每次请求时都会创建瞬态生命周期服务。”和“每个请求创建一次范围内的生命周期服务。”除非我对英语的掌握比我想象的要差,否则实际上意思是完全一样的。
  • @tmg 我知道。我只是指出文档在这一点上并不清楚,因此将人们指向文档并不是很有帮助。
  • @Neutrino,这就是我问这个问题的原因。
  • 聚会迟到了,甚至更晚才阅读 cmets,但我打印了那篇文章,阅读了它,并在页边空白处记下了我现在看到 @Neutrino 在这里所做的相同观察。这篇文章在提供该分析时完全含糊不清。幸运的是,这个例子没有那么令人困惑。
  • 据我了解:每次请求时都会创建瞬态生命周期服务。 requested 一词在这里是日常英语中要求某事的意思,在这种情况下是一项服务。而 once per request 中的词 request 指的是 HTTP 请求。但我确实理解这种困惑。

标签: c# asp.net-core .net-core


【解决方案1】:

TL;DR

瞬态对象总是不同的;一个新的实例被提供给 每个控制器和每个服务。

作用域对象在请求中相同,但在请求中不同 不同的请求。

单个对象对于每个对象和每个请求都是相同的。

为了更清楚,.NET documentation 的这个例子显示了不同之处:

为了演示这些生命周期和注册选项之间的区别,请考虑一个简单的接口,它将一个或多个任务表示为具有唯一标识符 OperationId 的操作。根据我们如何配置此服务的生命周期,容器将为请求类提供相同或不同的服务实例。为了明确请求哪个生命周期,我们将为每个生命周期创建一个类型选项:

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
    {
    }
}

我们使用单个类 Operation 实现这些接口,该类在其构造函数中接受 GUID,如果未提供则使用新的 GUID:

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中,每种类型都根据其命名的生命周期添加到容器中:

services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty));
services.AddTransient<OperationService, OperationService>();

请注意,IOperationSingletonInstance 服务正在使用已知 ID 为 Guid.Empty 的特定实例,因此在使用此类型时会很清楚。我们还注册了一个OperationService,它依赖于其他每个Operation 类型,这样在请求中就可以清楚地知道该服务是获取与控制器相同的实例还是新的实例,对于每个操作类型.该服务所做的只是将其依赖项作为属性公开,因此它们可以显示在视图中。

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,它请求每种IOperation 类型以及一个OperationService。然后,Index 操作会显示所有控制器和服务的 OperationId 值。

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 contains controller-requested services
            ViewBag.Transient = _transientOperation;
            ViewBag.Scoped = _scopedOperation;
            ViewBag.Singleton = _singletonOperation;
            ViewBag.SingletonInstance = _singletonInstanceOperation;

            // Operation service has its own requested services
            ViewBag.Service = _operationService;
            return View();
        }
    }
}

现在向这个控制器动作发出两个单独的请求:

观察哪个OperationId 值在请求内和请求之间变化。

  • 瞬态对象总是不同的;为每个控制器和每个服务提供一个新实例。

  • 作用域对象在一个请求中是相同的,但在不同的请求中是不同的

  • 每个对象和每个请求的单例对象都是相同的(不管ConfigureServices中是否提供了实例)

【讨论】:

  • 我了解它们每个的功能,但有人可以解释使用一个而不是另一个的影响。如果使用不当或选择一个而不是另一个,可能会导致什么问题。
  • 假设您正在创建一个具有单例范围的请求上下文相关对象(如当前用户),那么它将在所有不需要的 http 请求中保持相同的实例。 IOC 就是创建实例,所以我们需要指定创建实例的范围。
  • 您能否解释一下我们在单例中嵌套瞬态或范围依赖的常见陷阱?
  • 公平点!一般来说,如果我们将一个生命周期较短的对象放在一个生命周期较长的对象中,IoC 就不会再次创建内部对象。所以说如果你有一个单例,其中有一个瞬态或作用域对象,内部对象不会被重新创建,因为单例的构造函数不会被再次调用。但反过来也可以。您可以毫无问题地将单例放入瞬态。所以经验法则是内部对象的生命周期应该与外部对象相同或更长。
  • @akazemis 在这里做主的工作...清晰简洁的解释和带有指导示例的图形使插图回家。谢谢!
【解决方案2】:

在 .NET 的依赖注入中有三个主要的生命周期:

Singleton 在整个应用程序中创建单个实例。它首次创建实例并在所有调用中重用相同的对象。

Scoped 生命周期服务在范围内每个请求创建一次。它相当于当前范围内的单例。例如,在 MVC 中,它为每个 HTTP 请求创建一个实例,但它在同一个 Web 请求的其他调用中使用相同的实例。

瞬态生命周期服务在每次被请求时创建。此生命周期最适合轻量级、无状态的服务。

在这里你可以找到例子来看看区别:

ASP.NET 5 MVC6 Dependency Injection in 6 Steps(由于死链接导致的网页存档链接)

Your Dependency Injection ready ASP.NET : ASP.NET 5

这是官方文档的链接:

Dependency injection in ASP.NET Core

【讨论】:

  • 您能解释一下为什么 Transient 是最轻量级的吗?我认为 Transient 是最繁重的工作,因为它需要为每次注入创建一个实例。
  • 你是对的。 Transient 不是最轻量级的,我只是说它适合轻量级的 RESTful 服务 :)
  • 那么在哪种情况下我们可以使用作用域以及在控制器示例中的哪个瞬态,例如,如果我们从数据库中检索几行?在这种情况下,我试图了解作用域与瞬态使用场景。
  • 这真的取决于你所期望的逻辑。例如,如果它是一个单独的 db 调用,它实际上并不会影响您使用哪个数据库。但是,如果您在同一个请求中多次调用 db,那么您可以使用作用域生命周期,因为它将同一个存储库对象保留在内存中,并在同一个 Http Request 上下文中重复使用多次。而瞬态创建一个新的存储库对象多次(并消耗更多的内存)。如果您解释您的具体情况,就很容易判断哪一种更适合。
  • 这里要强调的一个重点是 Singleton、Scoped 和 Transient 就像俄罗斯的玩偶,一个又一个。例如,嵌套时不能颠倒它们的顺序。作用域或单例不能包含在瞬态中,因为我们将延长父级的生命周期,这违背了包含!
【解决方案3】:

使用哪个

瞬态

  • 因为每次创建它们都会使用更多内存和资源,并且可能对性能产生负面影响
  • 将此用于具有很少或无状态轻量级服务。

作用域

  • 当您想在请求中维护状态时更好的选择。

单例

  • 这些服务中的内存泄漏会随着时间的推移而增加。
  • 还可以节省内存,因为它们一旦在任何地方重复使用就可以创建。

在需要维护应用程序范围状态的地方使用单例。应用程序配置或参数、日志服务、数据缓存是您可以使用单例的一些示例。

将不同生命周期的服务注入另一个

  1. 永远不要将 Scoped & Transient 服务注入到 Singleton 服务中。(这会有效地将瞬态或作用域服务转换为单例服务。)

  2. 永远不要将瞬态服务注入作用域服务(这会将瞬态服务转换为作用域。)

【讨论】:

  • 这是最好的答案。我喜欢你举例的部分。理解它们的工作原理并不难。很难考虑将哪些服务放在哪里以及如何以及何时清除内存。如果您能对此进行更多解释,那就太好了。
  • 我不明白为什么Transient 被推荐用于“很少或没有状态的轻量级服务”。在这种情况下为什么不单例?因为它是无状态的,所以只实例化那个小服务并多次使用它不是更好吗?即使服务实例化很便宜,如果你做很多次,开销也会增加。使用单例,它保持不变
  • 应该补充一点,当使用单例时,你必须确保它们是线程安全的,因为它们可以被运行在不同线程上的多个并发请求使用。
  • 将临时服务注入作用域服务有什么问题?据我了解,这样做不会使瞬态服务变成单例(如果您在其他地方注入了相同的瞬态服务,它将是一个不同的对象),因此只要瞬态服务没有状态(这应该是隐含的),我没有看到问题。
  • @S-eagle 你能否举一个这样的无状态类的例子,如果它是按请求实例化的(瞬态),它将获得一些性能?我真的很想对此有一个很好的了解。
【解决方案4】:

Transient, scopedsingleton 定义了 ASP.NET MVC core DI(Dependency Injection) 中需要注入多个相同类型的对象时的对象创建过程。如果您不熟悉依赖注入,您可以查看DI IoC video

您可以看到下面的控制器代码,其中我在构造函数中请求了两个 "IDal" 实例。 Transient、ScopedSingleton 定义是否将同一实例注入 "_dal""_dal1" 或不同。

public class CustomerController : Controller
{
    IDal dal = null;

    public CustomerController(IDal _dal,
                              IDal _dal1)
    {
        dal = _dal;
        // DI of MVC core
        // inversion of control
    }
}

瞬态:在瞬态中,新的对象实例将被注入到单个请求和响应中。下面是我显示 GUID 值的快照图像。

Scoped:在Scoped中,同一个对象实例将被注入到单个请求和响应中。

单例:在单例中,将在所有请求和响应中注入同一个对象。在这种情况下,将创建一个对象的全局实例。

下面是一个简单的图表,直观地解释了上述基本原理。

上图是我拍摄ASP.NET MVC training in Mumbai时SBSS团队绘制的。非常感谢 SBSS 团队制作了上述图片。

【讨论】:

  • 这是我见过的对瞬态服务最复杂的解释。瞬态 = 任何时候解决此服务都相当于分配您的变量 new TService。 Scoped 将为该“范围”缓存它的第一次初始化(大多数情况下为 http 请求)。 Singleton 将在应用程序的整个生命周期内只缓存一个实例,就这么简单。上面的图太复杂了。
  • 很抱歉,我想我会用图表和代码快照让它更简单:-) 但我明白你的意思。
  • 我发现这在您注入多个实例并使用瞬态注册的独特情况下很有帮助。谢谢
【解决方案5】:

这张图片很好地说明了这个概念。 不幸的是,我找不到这个图像的来源,但是有人制作了它,他以图像的形式很好地展示了这个概念。

更新:图片参考:ASP.NET Core Service Lifetimes (Infographic),作者:@WaqasAnwar

【讨论】:

  • 这里是上图的原始出处。 ezzylearning.net/tutorial/… 其实我是 5 天前在我的博客上发布的 :-)
  • 我读了你的文章,我有很多这些services.AddTransient&lt;IProductService, ProductService&gt;();。我有一项服务,内存中的计数为 193!这个服务只有无状态的方法,它应该是作用域而不是瞬态的,所以我只能为我的所有控制器创建一个?
  • @MikeFlynn 对于每个请求一个实例,您应该使用AddScoped&lt;IProductService, ProductService&gt;();。但是对于所有请求的一个实例,请使用AddSingelton&lt;IProductService, ProductService&gt;();
  • 在应用程序重新启动之前不会将单例保存在内存中吗?我不希望有大量单身人士在附近闲逛。
【解决方案6】:
  • 单例是应用程序生命周期内的单个实例 域。
  • 作用域是作用域持续时间内的单个实例 请求,这意味着 ASP.NET 中的每个 HTTP 请求。
  • Transient 是每个代码请求的单个实例。

通常代码请求应通过构造函数参数进行,如

public MyConsumingClass(IDependency dependency)

我想在@akazemis 的回答中指出,DI 上下文中的“服务”并不意味着 RESTful 服务;服务是提供功能的依赖项的实现。

【讨论】:

    【解决方案7】:

    AddSingleton()

    AddSingleton() 在第一次被请求时创建一个服务实例,并在所有需要该服务的地方重用同一个实例。

    AddScoped()

    在作用域服务中,对于每个 HTTP 请求,我们都会获得一个新实例。但是,在同一个 HTTP 请求中,如果在多个地方需要服务,例如在视图和控制器中,则为该 HTTP 请求的整个范围提供相同的实例。但是每个新的 HTTP 请求都会获得一个新的服务实例。

    AddTransient()

    对于临时服务,每次请求服务实例时都会提供一个新实例,无论它是在同一个 HTTP 请求的范围内还是跨不同的 HTTP 请求。

    【讨论】:

      【解决方案8】:

      在寻找这个问题的答案后,我找到了一个精彩的解释,并附有一个我想与你分享的例子。

      您可以观看演示差异的视频HERE

      在这个例子中,我们有这个给定的代码:

      public interface IEmployeeRepository
      {
          IEnumerable<Employee> GetAllEmployees();
          Employee Add(Employee employee);
      }
      
      public class Employee
      {
          public int Id { get; set; }
          public string Name { get; set; }
      }
      
      public class MockEmployeeRepository : IEmployeeRepository
      {
          private List<Employee> _employeeList;
      
          public MockEmployeeRepository()
          {
              _employeeList = new List<Employee>()
          {
              new Employee() { Id = 1, Name = "Mary" },
              new Employee() { Id = 2, Name = "John" },
              new Employee() { Id = 3, Name = "Sam" },
          };
          }
      
          public Employee Add(Employee employee)
          {
              employee.Id = _employeeList.Max(e => e.Id) + 1;
              _employeeList.Add(employee);
              return employee;
          }
      
          public IEnumerable<Employee> GetAllEmployees()
          {
              return _employeeList;
          }
      }
      

      HomeController

      public class HomeController : Controller
      {
          private IEmployeeRepository _employeeRepository;
      
          public HomeController(IEmployeeRepository employeeRepository)
          {
              _employeeRepository = employeeRepository;
          }
      
          [HttpGet]
          public ViewResult Create()
          {
              return View();
          }
      
          [HttpPost]
          public IActionResult Create(Employee employee)
          {
              if (ModelState.IsValid)
              {
                  Employee newEmployee = _employeeRepository.Add(employee);
              }
      
              return View();
          }
      }
      

      创建视图

      @model Employee
      @inject IEmployeeRepository empRepository
      
      <form asp-controller="home" asp-action="create" method="post">
          <div>
              <label asp-for="Name"></label>
              <div>
                  <input asp-for="Name">
              </div>
          </div>
      
          <div>
              <button type="submit">Create</button>
          </div>
      
          <div>
              Total Employees Count = @empRepository.GetAllEmployees().Count().ToString()
          </div>
      </form>
      

      Startup.cs

      public void ConfigureServices(IServiceCollection services)
      {
          services.AddMvc();
          services.AddSingleton<IEmployeeRepository, MockEmployeeRepository>();
      }
      

      复制粘贴此代码,然后按视图中的创建按钮并在两者之间切换 AddSingletonAddScopedAddTransient 您每次都会得到不同的结果,这可能有助于您理解这一点。

      AddSingleton() - 顾名思义,AddSingleton() 方法创建一个 单例服务。一个 Singleton 服务是在第一次创建时创建的 请求。然后所有后续的实例都会使用这个相同的实例 要求。所以一般来说,一个 Singleton 服务只创建一次 每个应用程序,并且该单个实例在整个 应用程序生命周期。

      AddTransient() - 此方法创建一个 Transient 服务。一个新的 每次请求时都会创建 Transient 服务的实例。

      AddScoped() - 此方法创建一个 Scoped 服务。一个新的实例 范围内的每个请求都会创建一次 Scoped 服务。为了 例如,在 Web 应用程序中,它会为每个 http 创建 1 个实例 请求但在同一实例中的其他调用中使用相同的实例 网络请求。

      【讨论】:

      • 加 1 为作者提供学分。 :)
      • 我感觉没有人使用我的代码,每个人都继续观看附加视频:)
      • @OffirPe'er 不是我!感谢您提供详细信息。
      【解决方案9】:
      • 瞬态:每次出现一个新实例时都会提供一个新实例 请求实例是否在同一个http请求的范围内 或跨不同的 http 请求。

      • Scoped:我们在一个范围内得到相同的实例 给定的 http 请求,但是一个跨不同 http 请求的新实例。

      • Singleton:只有一个实例。一个实例 在首次请求服务并且该单个实例时创建 所有后续的 http 请求都将使用单个实例 在整个应用程序中。

      【讨论】:

        猜你喜欢
        • 2022-11-12
        • 1970-01-01
        • 2021-09-07
        • 2019-12-19
        • 2015-01-24
        • 2017-01-13
        • 2020-09-16
        • 2012-12-11
        相关资源
        最近更新 更多