【问题标题】:Lazy Dependency Injection惰性依赖注入
【发布时间】:2012-09-05 15:09:42
【问题描述】:

我有一个项目,其中 Ninject 用作​​ IoC 容器。我担心的是很多类都有这样的构造函数:

[Inject]
public HomeController(
    UserManager userManager, RoleManager roleManager, BlahblahManager blahblahManager) {
   _userManager = userManager;
   _roleManager = roleManager;
   _blahblahManager = blahblahManager;
}

如果我不想一次拥有这些类的所有实例怎么办?

当所有这些类都由Lazy<T> 包装并传递给构造函数时,这并不是我所需要的。 T 实例尚未创建,但 Lazy<T> 实例已存储在内存中。

我的同事建议我使用工厂模式来控制所有实例化,但我不确定 IoC 是否有这么大的设计错误。

对于这种情况是否有解决方法,或者 IoC 的设计真的有这么大的缺陷?也许我应该使用另一个 IoC 容器?

有什么建议吗?

【问题讨论】:

  • 在控制器工作期间我可能需要 UserManager,但不需要 RoleManager,反之亦然。如果您谈论的是 Lazy 实例,那么将它们放在内存中并不是什么大问题,但这是唯一的方法吗?
  • 为什么UserManagerRoleManager 很重要?无论如何,您的构造函数不应该做繁重的工作。
  • 它们存储在内存中,如果它们真的很复杂,可能会导致性能问题。此外,该示例仅包含三个注入的类,但如果还有更多...
  • @DanielHilgarth - 是的,虽然这意味着您不需要创建明确的工厂类型。
  • 每个容器都允许您注入Func<T>Lazy<T>。只需在容器中手动注册 Func<T>Lazy<T>

标签: c# .net design-patterns inversion-of-control ninject


【解决方案1】:

在我看来,你在做premature optimization:不要这样做。

你的服务的构造函数应该做nothing more而不是存储它在私有字段中获取的依赖项。在那种情况下,创建这样一个对象真的很轻。不要忘记 .NET 中的对象创建速度非常快。在大多数情况下,从性能的角度来看,这些依赖项是否被注入并不重要。尤其是在与应用程序的其余部分(以及您使用的框架)吐出的对象数量进行比较时。真正的成本是当您开始使用 Web 服务、数据库或文件系统(或一般的 I/O)时,因为它们会导致更大的延迟。

如果创建真的很昂贵,您通常应该将创建隐藏在Virtual Proxy 后面,而不是在每个消费者中注入Lazy<T>,因为这允许常见的应用程序代码忽略存在一种机制延迟创建(当您这样做时,您的应用程序代码和测试代码都变得更加复杂)。

Dependency Injection: Principle, Practices, Patterns 的第 8 章包含关于惰性代理和虚拟代理的更详细讨论。

但是,Lazy<T> 只消耗 20 字节的内存(另一个 24 bytes 用于包装 Func<T>,假设是 32 位进程),Lazy<T> 实例的创建实际上是免费的。因此,无需担心这一点,除非您处于内存限制非常严格的环境中。

如果内存消耗是一个问题,请尝试注册生命周期大于瞬态的服务。您可以执行每个请求、每个 Web 请求或单例。我什至会说,当您处于创建新对象成为问题的环境中时,您可能应该only use singleton services(但您不太可能在这样的环境中工作,因为您正在构建一个 Web 应用程序)。

请注意,Ninject 是 .NET 中速度较慢的 DI 库之一。如果这让您感到困扰,switch to a faster container。一些容器的性能接近于手动更新对象图。 但无论如何,请务必对此进行分析,许多开发人员出于错误的原因切换 DI 库。

请注意,使用Lazy<T> 作为依赖项是leaky abstraction(违反Dependency Inversion Principle)。请阅读this answer了解更多信息。

【讨论】:

  • 我不认为这是过早的优化,有了这个论点似乎永远不需要懒惰,创建一个对象并不重,但你不知道它在它的创建了多少个对象构造函数。并打开一些可能不会被使用的远程资源。
【解决方案2】:

Steven 说这看起来像是过早的优化是正确的。这些对象的构建速度非常快,通常不会成为瓶颈。

然而,使用 Lazy 来表达你不需要的依赖是依赖注入框架中的常见模式。 Actofac 就是这样一种容器,它内置了对 various wrapping types 的支持。我相信 Ninject 也有一个扩展,也许可以看看这个,Ninject Lazy

【讨论】:

  • DI 容器支持 Lazy 的事实并不能让您的应用程序代码依赖于 Lazy 依赖项。许多 DI 容器支持不促进最佳实践的功能。从依赖注入的角度来看,Lazy 是一种泄漏抽象。请阅读 this 了解 Lazy 泄漏的原因。
【解决方案3】:

您还可以使用以下语法注入到操作方法中。 (我不确定这是什么版本)。

构造函数是最佳实践,但是当我有一个服务正在进行一些昂贵的初始化时,我不得不故意这样做 - 事实上是偶然的 - 但有一段时间没有发现它将其移至确实需要它的一种方法是最简单的。

如果您只需要从一个操作方法访问服务,这可以使代码更简洁 - 但请记住,如果您将其注入到该方法中,您将不得不到处传递它,因为它将不再位于 @ 987654322@。绝对不要在操作方法中分配给this.service - 这太可怕了。

public IActionResult About([FromServices] IDateTime dateTime)
{
    ViewData["Message"] = "Currently on the server the time is " + dateTime.Now;

    return View();
}

https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/dependency-injection?view=aspnetcore-2.2#action-injection-with-fromservices

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-07-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-08
    • 1970-01-01
    • 2022-06-27
    • 2015-07-07
    相关资源
    最近更新 更多