AddTransient,AddScoped和AddSingleton服务的差异?

Elv*_*dov 746 c# .net-core asp.net-core

我想要实现dependency injectionAsp.Net Core.因此,在将此代码添加到ConfigureServices方法之后,两种方式都有效.

services.AddTransientservice.AddScoped方法有Asp.Net Core什么区别?

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

    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddScoped<IEmailSender, AuthMessageSender>();
}
Run Code Online (Sandbox Code Playgroud)

aka*_*mis 1320

TL; DR

瞬态物体总是不同的; 为每个控制器和每个服务提供一个新实例.

范围内的对象在请求中是相同的,但在不同的请求中是不同的.

Singleton对象对于每个对象和每个请求都是相同的.

有关更多说明,来自asp.net docs的此示例显示了不同之处:

为了演示这些生命周期和注册选项之间的区别,请考虑一个简单的接口,它将一个或多个任务表示为具有唯一标识符的操作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
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

我们使用单个类来实现这些接口Operation,它Guid在其构造函数中接受a ,或者Guid如果没有提供则使用new .

接下来,在ConfigureServices每个类型根据其命名生命周期添加到容器中:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

为了演示对应用程序的单独单个请求内和之间的对象生存期,该示例包括OperationsController请求每种IOperation类型以及a 的类型OperationService.Index然后,该操作将显示所有控制器和服务的OperationId值.

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,对此控制器操作发出两个单独的请求: 第一次请求

第二个请求

观察OperationId请求中和请求之间的哪些值有所不同.

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

  • 范围内的对象在请求中是相同的,但在不同的请求中是不同的

  • Singleton对象对于每个对象和每个请求都是相同的(无论是否提供实例ConfigureServices)

  • 有道理!一般来说,如果我们将一个生命周期较短的对象放入一个生命周期较长的对象中,IoC 不会再次创建内部对象。所以说,如果您有一个单例,其中包含瞬态或作用域对象,则不会重新创建内部对象,因为单例的构造函数不会再次被调用。但反过来也可以。您可以毫无问题地将单例放入瞬态中。因此,经验法则是内部对象的生命周期应等于或更长于外部对象的生命周期。 (20认同)
  • 假设您正在创建一个具有单例范围的请求上下文相关对象(如当前用户),那么它将在所有不需要的 http 请求中保持相同的实例。IOC 就是创建实例,所以我们需要指定创建的实例的范围是什么。 (12认同)
  • @akazemis 在这里做主的工作...清晰简洁的解释和带有指导示例的图形将插图带回家。谢谢你! (8认同)
  • 我理解它们每个的功能,但是有人可以解释使用其中一个而不是另一个的影响。如果使用不正确或选择一个而不是另一个,可能会导致什么问题。 (5认同)
  • 请稍等,这是文档的确切副本吗?) (4认同)
  • 您还能解释一下我们在单例中嵌套瞬态或范围依赖项的常见陷阱吗? (2认同)
  • @YaroslavTrofimov 说实话,我没有得到矛盾的部分,是的,确实如此,每个请求都会创建一个新的控制器实例并调用其相关方法,但控制器所依赖的服务(操作)可能位于不同的范围内。这很好。这样想吧,假设在控制器方法中有一个循环,其中每次迭代都需要创建一个对象的新实例,如果该对象的生命周期是有范围的,它将创建该对象一次并多次重用它,而如果它是暂时的,它会创建多个对象, (2认同)

aka*_*mis 288

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

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

作用域是每个范围内的请求曾经创造终身服务.它相当于当前范围内的Singleton.例如.在MVC中,它为每个http请求创建1个实例,但在同一Web请求中的其他调用中使用相同的实例.

每次请求时都会创建瞬态生命周期服务.这种生命周期最适合轻量级无状态服务.

在这里你可以找到和看到差异的例子:

http://dotnetliberty.com/index.php/2015/10/15/asp-net-5-mvc6-dependency-injection-in-6-steps/

https://codewala.net/2015/04/30/your-dependency-injection-ready-asp-net-asp-net-5/

这是官方文档的链接:

https://docs.asp.net/en/latest/fundamentals/dependency-injection.html#service-lifetimes-and-registration-options

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

ber*_*gle 146

使用哪一个

短暂的

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

有范围的

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

单身人士

  • 这些服务中的内存泄漏会随着时间的推移而增加。
  • 内存效率也很高,因为它们是在任何地方重用后创建的。

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

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

  1. 永远不要将 Scoped & Transient 服务注入 Singleton 服务。(这有效地将瞬态或范围服务转换为单例。)
  2. 永远不要将瞬态服务注入范围服务(这会将瞬态服务转换为范围服务。)

  • 这是最好的答案。我喜欢你举例的部分。理解它们的工作原理并不难。考虑将哪些服务放在哪里以及如何以及何时清除它们的内存要困难得多。如果您能对此进行更多解释,那就太好了。 (12认同)
  • 应该补充的是,使用单例时,必须确保它们是线程安全的,因为它们可以被在单独线程上运行的多个并发请求使用。 (5认同)
  • @S-eagle 你能举一个这样的无状态类的例子吗?如果它根据请求(瞬态)实例化,它将获得一些性能?我真的很想对此有一个很好的理解。 (5认同)
  • 我不明白为什么“Transient”被推荐用于“很少或没有状态的轻量级服务”。在这种情况下为什么不单例呢?由于该小服务是无状态的,因此仅实例化一次并多次使用它不是更好吗?即使服务实例化很便宜,如果执行多次,开销也会增加。对于单例,它保持不变 (4认同)
  • 如果您不应该将瞬态服务注入到范围内或单例中,那么您将在哪里注入瞬态服务? (3认同)
  • 永远不要“将不同生命周期的服务注入另一个生命周期”的建议是无稽之谈。将对象设置为 Transient 的原因是因为您永远不希望一个对象被多个对象共享。在 Singleton 或 Scoped 对象中使用 Transient 对象没有任何问题。假设你有一个缓存对象,你可以在全局 Singleton 中拥有一个缓存,在两个不同的 Scoped 对象中拥有另一个缓存。在所有这些情况下,您希望每个对象都有自己的缓存,实现这一点的方法是使缓存瞬态。 (3认同)
  • 将瞬态服务注入作用域服务有什么问题?根据我的理解,这样做不会使瞬态服务_成为_单例(如果您在其他地方注入相同的瞬态服务,它将是一个不同的对象),因此假设瞬态服务没有状态(应该是隐式的),我没看出有什么问题。 (2认同)

Ham*_*aei 32

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

  • 这是上图的原始来源。https://www.ezzylearning.net/tutorial/asp-net-core-service-lifetimes-infographic/ 实际上我 5 天前将其发布在我的博客上:-) (10认同)

Shi*_*ala 27

当必须注入多个相同类型的对象时,瞬态,作用域和单例定义ASP.NET MVC核心DI中的对象创建过程.如果您不熟悉依赖注入,您可以看到这个DI IOC视频

你可以看到下面的控制器代码,我在构造函数中请求了两个"IDal"实例.Transient,Scoped和Singleton定义是否在"_dal"和"_dal1"中注入相同的实例或不同.

public class CustomerController : Controller
    {
        IDal dal = null;
        public CustomerController(IDal _dal
                                ,IDal _dal1)
        {
            dal = _dal;
            // DI of MVC core
            // inversion of control
        }
}
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述

范围: - 在范围内相同的对象实例将被注入单个请求和响应.

在此输入图像描述

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

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

MVC DI图像

上面的图片由SBSS团队绘制,当我在孟买培训中进行ASP.NET MVC培训时,非常感谢SBSS团队创建上述图像.

  • 这是我见过的瞬态服务的最复杂的解释.瞬态=任何时候解决此服务相当于分配变量`new TService`.Scoped将为该"范围"缓存它的第一次初始化(在大多数情况下为http请求).Singleton将在应用程序的生命周期内仅缓存一个实例,就像那样简单.上面的图表很复杂. (6认同)
  • 对不起我以为我会用图表和代码快照让它变得更简单:-)但我确实明白你的观点. (2认同)

use*_*177 27

  • Singleton是应用程序域生命周期的单个实例.
  • Scoped是作用域请求持续时间内的单个实例,这意味着ASP.NET中的每个HTTP请求.
  • 瞬态是每个代码请求的单个实例.

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

public MyConsumingClass(IDependency dependency)
Run Code Online (Sandbox Code Playgroud)

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


Off*_*'er 20

在寻找这个问题的答案后,我找到了一个很好的解释,我想与你分享一个例子。

您可以在此处观看演示差异的视频

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

家庭控制器

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

创建视图

@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>
Run Code Online (Sandbox Code Playgroud)

启动文件

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddSingleton<IEmployeeRepository, MockEmployeeRepository>();
}
Run Code Online (Sandbox Code Playgroud)

复制粘贴此代码,然后按上的观点和开关按钮创建 AddSingletonAddScoped并且AddTransient每一次你会得到不同的结果,这将有助于你了解这一点。

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

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

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

  • 我有一种感觉,没有人使用我的代码,每个人都继续观看附加的视频:) (4认同)
  • 加 1 表示对作者的认可。:) (3认同)

小智 17

添加到这里所有精彩的答案。这个答案将解释不同的生命周期以及何时适合选择它们。与软件开发中的许多事情一样,没有绝对的事情,许多条件都会影响最佳选择,因此请将此答案视为一般指导。

\n

ASP.NET Core 附带了自己的内置依赖注入容器,它使用它来解析请求生命周期中所需的服务。所有框架服务,例如日志记录、配置、路由等,都使用依赖注入,并且在构建应用程序主机时将它们注册到依赖注入容器中。在内部,ASP.NET Core 框架提供激活框架组件(例如控制器和 Razor 页面)时所需的依赖项。

\n

依赖注入容器(有时称为控制反转或 IoC 容器)是管理对象实例化和配置的软件组件。依赖注入容器并不是应用依赖注入模式的必要条件,但随着应用程序的增长,使用依赖注入容器可以极大地简化依赖项的管理,包括它们的生命周期。服务在启动时向容器注册,并在运行时在需要时从容器解析。容器负责创建和处置所需服务的实例,并在指定的生命周期内维护它们。

\n

使用 Microsoft 依赖注入容器时,我们主要针对两个接口进行编码。

\n
    \n
  1. IServiceCollection 接口定义了用于注册和配置服务描述符集合的契约。我们从 IServiceCollection 构建一个 IServiceProvider。
  2. \n
  3. IServiceProvider,定义了一种在运行时解析服务的机制。
  4. \n
\n

当向容器注册服务时,应该为服务选择服务生命周期。服务生命周期控制已解析对象在容器创建后的生存时间。注册服务时,可以通过在 IServiceCollection 上使用适当的扩展方法来定义生命周期。Microsoft 依赖注入容器可以使用三个生命周期。

\n
    \n
  1. 短暂的
  2. \n
  3. 辛格尔顿
  4. \n
  5. 范围
  6. \n
\n

依赖注入容器跟踪它创建的所有服务实例,一旦它们的生命周期结束,它们就会被处理或释放以进行垃圾回收。所选择的生命周期会影响同一服务实例是否可以被解析并注入到多个依赖的消费者中。因此,明智地选择服务的生命周期至关重要。

\n

临时服务

\n

当服务注册为 Transient 时,每次解析该服务时,容器都会创建并返回该服务的新实例。换句话说,每个通过容器注入接受 Transient 服务的依赖类都将收到自己唯一的实例。因为每个依赖类都会接收自己的实例,所以实例上的方法可以安全地改变内部状态,而不必担心其他使用者和线程的访问。

\n

临时服务的用途/特征

\n
    \n
  1. 当服务包含可变状态并且不被视为线程安全时,瞬态服务最有用。
  2. \n
  3. 使用瞬态服务可能会带来很小的性能成本,尽管对于一般负载下的应用程序来说,该成本可能可以忽略不计。
  4. \n
  5. 每当解析一个实例时(可能与每个请求一样频繁),都必须为该实例分配内存。这导致垃圾收集器需要进行额外的工作来清理那些短命的对象。
  6. \n
  7. 瞬态服务是最容易理解的,因为实例永远不会共享;因此,当您注册服务时不清楚哪个生命周期是最佳选择时,它们往往是最安全的选择。
  8. \n
\n

单例服务

\n

使用单例生命周期注册的应用程序服务在依赖注入容器的生命周期内只会创建一次。在 ASP.NET Core 中,这相当于应用程序的生命周期。第一次需要服务时,容器将创建一个实例。之后,可以重用同一实例并将其注入到所有依赖类中。该实例将在容器的生命周期内保持可达状态,因此不需要处置或垃圾收集。

\n

单例服务的用途/特点

\n
    \n
  1. 假设频繁需要该服务,例如每次请求。这可以通过避免重复分配新对象来提高应用程序性能,每个新对象可能稍后才需要垃圾回收。
  2. \n
  3. 此外,如果对象的构造成本很高,则将其限制为单个实例可以提高应用程序性能,因为它仅在第一次使用服务时发生一次。
  4. \n
  5. 当选择以 Singleton 生命周期注册服务时,必须考虑线程安全。因为单例服务的同一个实例可以被多个请求同时使用。任何没有合适锁定机制的可变状态都可能导致意外行为。
  6. \n
  7. Singleton 非常适合函数式服务,其中方法接受输入并返回输出,不使用共享状态。
  8. \n
  9. ASP.NET Core 中单例生命周期的一个合理用例是内存缓存,其中必须共享状态才能使缓存发挥作用。
  10. \n
  11. 为 Singleton 注册服务时,请考虑在应用程序的生命周期内保留分配的单个实例的影响。\na。如果实例拥有大量内存,则可能会造成内存泄漏。\nb。如果内存使用量在实例\xe2\x80\x99s 生命周期内增长,这可能会特别成问题,因为它永远不会被释放用于垃圾回收。\nc。如果某个服务对内存要求很高,但使用频率很低,那么单例生存期可能不是最合适的选择。
  12. \n
\n

范围服务

\n

作用域服务处于 Transient 和 Singleton 之间的中间地带。作用域服务的实例的生存期与解析它的作用域的长度相同。在 ASP.NET Core 中,应用程序中会为其处理的每个请求创建一个作用域。任何范围服务都将为每个范围创建一次,因此它们的行为与单例服务类似,但在范围的上下文中。在处理特定请求时,所有框架组件(例如中间件和 MVC 控制器)都会获取作用域服务的相同实例。

\n

范围服务的用途/特征

\n
    \n
  1. 容器为每个请求创建一个新实例。
  2. \n
  3. 由于容器会根据请求解析该类型的新实例,因此通常不需要线程安全。
  4. \n
  5. 请求生命周期内的组件是按顺序调用的,因此共享实例不会并发使用。
  6. \n
  7. 如果多个使用者在请求期间可能需要相同的依赖项,则作用域服务非常有用。\na。这种情况的一个很好的例子是使用 Entity Framework Core。默认情况下,DbContext 是在作用域生命周期内注册的。因此,更改跟踪适用于整个请求。多个组件可以对共享的 DbContext 进行更改。
  8. \n
\n

避免强制依赖

\n

注册依赖项时,考虑到服务本身具有的任何依赖项,确保选择的生命周期是适当的至关重要。这是必要的,以避免所谓的“强制依赖”,即服务的生存时间可能比预期的要长。

\n

经验法则是,服务不应依赖于生命周期比其自身短的服务。例如,使用单例生命周期注册的服务不应依赖于瞬态服务。这样做会导致瞬态服务被单例服务捕获,并在应用程序的生命周期中无意中引用该实例。这可能会导致出现问题,有时甚至难以追踪运行时错误和行为,例如意外地在线程之间共享非线程安全服务或允许对象超过其预期生命周期。\n为了形象化这一点,让我们考虑哪些生命周期可以安全地依赖使用另一个生命周期的服务。

\n
    \n
  1. 由于它将是一个短期服务,因此瞬态服务可以安全地依赖于具有瞬态、作用域或单例生命周期的服务
  2. \n
  3. 范围服务有点棘手。如果它们依赖于瞬态服务,则该瞬态服务的单个实例将在整个请求的范围生命周期内存活。您可能想要也可能不想要这种行为。为了绝对安全,您可以选择不依赖作用域服务中的瞬态服务,但作用域服务依赖于其他作用域服务或单例服务是安全的。
  4. \n
  5. 单例服务在其依赖性方面受到最大的限制,它不应该依赖于瞬态或范围内的服务,但可以依赖于其他单例服务。单例捕获范围服务是更危险的可能性之一。因为作用域服务可以在作用域结束后被处置,所以单例可能会在处置后尝试访问它们。这可能会导致生产中出现运行时异常,这是一种非常糟糕的情况。
  6. \n
\n

在此输入图像描述

\n


Mar*_*ese 13

DI 容器一开始可能非常令人困惑,尤其是在生命周期方面。毕竟,容器使用反射来使一切“正常工作”。它有助于思考容器实际上在幕后为您完成什么:组成对象图。

对于 .NET Web 应用程序,使用 DI 容器的替代方法是将默认控制器激活器替换为您自己的控制器激活器,该激活器必须管理生命周期并手动构建依赖关系图。出于学习目的,假设您有一个控制器激活器,该激活器经过硬编码以在每次出现 Web 请求时返回一个特定控制器:

// This class is created once per application during startup.  In DI terms, it is the
// "composition root."
public class DumbControllerActivator
{
    // Shared among all consumers from all requests
    private static readonly Singleton1 singleton1 = new Singleton1();
    private static readonly Singleton2 singleton2 = new Singleton2();

    // This method's responsibility is to construct a FooController and its dependecies.
    public FooController HandleFooRequest()
    {
        // Shared among all consumers in this request
        var scoped1 = new Scoped1();
        var scoped2 = new Scoped2(singleton1, scoped1);

        return new FooController(
            singleton1,
            scoped1,
            new Transient1(                     // Fresh instance
                singleton2,
                new Transient2(scoped2)),       // Fresh instance
            new Transient3(                     // Fresh instance
                singleton1,
                scoped1,
                new Transient1(                 // Fresh instance
                    singleton2,
                    new Transient2(scoped2)));  // Fresh instance
    }
}
Run Code Online (Sandbox Code Playgroud)
  • 激活器仅创建每个单例实例一次,然后在应用程序的整个生命周期中保留它。每个消费者共享该单个实例(甚至来自不同请求的消费者)。
  • 对于范围内的依赖项,激活器为每个 Web 请求创建一个实例。在该请求中,每个消费者共享该单个实例,但从请求到请求,实例是不同的。
  • 对于暂时的依赖关系,每个消费者都有自己的私有实例。根本没有共享。

为了更深入地了解 DI,我强烈推荐《依赖注入原理、实践和模式》一书。我的回答基本上只是重复我在那里学到的东西。


小智 8

我喜欢画我能理解的东西。

在此输入图像描述

在此输入图像描述

在此输入图像描述


Cem*_*aya 8

有很棒的答案。我想通过类比来简单介绍一下。

想象一下,您经营一家咖啡店,拥有三种类型的员工:咖啡师、收银员和经理。每种类型的员工代表咖啡店应用程序中具有不同生命周期的不同依赖关系。

  1. 短暂的生命周期(咖啡师):

    • 临时服务就像为每个走进咖啡店的顾客雇用一名新的咖啡师。
    • 当顾客进来时,您会聘请一位新咖啡师来为他们制作咖啡。顾客离开后,该咖啡师就不再需要,可以被解雇。
    services.AddTransient<IBarista, Barista>();
    
    Run Code Online (Sandbox Code Playgroud)
  2. 范围生命周期(出纳员):

    • 范围服务就像在咖啡店雇用一名收银员进行单班制。
    • 当顾客在轮班期间进来时,同一位收银员会处理他们的所有交易。一旦轮班结束,收银员就可以回家,并为下一个轮班雇用新的收银员。
    services.AddScoped<ICashier, Cashier>();
    
    Run Code Online (Sandbox Code Playgroud)
  3. 单例生命周期(经理):

    • 单例服务就像有一个经理随时监督您的咖啡店。
    • 经理负责管理咖啡店内的一切,并在咖啡店营业期间保持原位。
    services.AddSingleton<IManager, Manager>();
    
    Run Code Online (Sandbox Code Playgroud)

现在,让我们应用这个类比来从数据库中检索项目:

  • 瞬态生命周期(咖啡师):如果将服务配置为瞬态,则每次需要时都会创建该服务的新实例。在数据库上下文中,为每个数据库查询创建一个新的数据库连接或数据访问组件。查询完成后,该连接将被关闭并释放。

  • 作用域生命周期(Cashiers):如果将服务配置为作用域,则在操作或请求期间将创建该服务的单个实例。在数据库上下文中,创建单个数据库连接或数据访问组件,并在同一操作或请求内的所有查询之间共享。一旦操作或请求完成,连接就会关闭并被释放。

  • 单例生命周期(管理器):如果将一项服务配置为单例,则在整个应用程序的生命周期中仅创建和共享该服务的一个实例。在数据库上下文中,将创建单个数据库连接或数据访问组件,并将其用于从应用程序启动到关闭的所有查询。

选择使用哪个生命周期取决于特定应用场景中的资源效率、隔离要求和并发注意事项等因素。不同的生命周期提供不同的权衡,因此必须选择符合应用程序要求和性能限制的生命周期。

除了这些信息之外,我还构建了一个网络项目来根据其他用户提供的上述精彩信息来测试它们。

这是网络应用程序的结果:

依赖注入生命周期

正如您在图片中看到的,两个不同的请求Index和,为瞬态作用域Privacy返回不同的结果。

如果有人想知道这些代码,这是我创建的应用程序。

源代码在GitHub


Yas*_*ser 6

AddSingleton()

在首次请求该服务时,AddSingleton()将创建该服务的单个实例,并在需要该服务的所有位置重用该实例。

AddScoped()

在具有每个http请求的范围服务中,我们获得了一个新实例。但是,在同一个http请求中,如果在多个位置(如视图和控制器中)需要服务,则将为该http请求的整个范围提供相同的实例。但是,每个新的http请求都将获得该服务的新实例。

AddTransient()

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


Arv*_*iya 6

.NET Core所有这些技术中AddTransientAddScopedAddSingleton定义注入对象的生命周期。

AddSingleton- 在整个应用程序或服务器中,只会创建该类的一个全局实例。

builder.Services.AddSingleton<ConsoleLogger>();
Run Code Online (Sandbox Code Playgroud)

AddScoped- 对于每个请求,都会创建不同的实例,但在同一个请求中,如果您要求依赖项注入对象两次、三次,则将提供相同的实例,所有对象都将相同。请参阅下面的示例

builder.Services.AddScoped<ConsoleLogger>();
Run Code Online (Sandbox Code Playgroud)

现在在控制器类中

ConsoleLogger _logger;
public HomeController( ConsoleLogger logger,  ------ Same object
                       ConsoleLogger logger1) ------ Same object
{
    _logger = consoleLogger;
}
Run Code Online (Sandbox Code Playgroud)

这里logger等于logger1.

AddTransient- 对于每个请求,即使在请求内,如果您多次要求 DI 创建对象,也会创建新实例,每次都会创建新实例。请参阅下面的示例

builder.Services.AddTransient<ConsoleLogger>();
Run Code Online (Sandbox Code Playgroud)

现在在控制器类中

ConsoleLogger _logger;
public HomeController( ConsoleLogger logger,  ------ New object
                       ConsoleLogger logger1) ------ New object
{
    _logger = consoleLogger;
}
Run Code Online (Sandbox Code Playgroud)

这里logger不等于logger1。希望能帮助到你。