【问题标题】:.NET core - Dependency Injection, factories and IDisposable.NET 核心 - 依赖注入、工厂和 IDisposable
【发布时间】:2021-03-02 21:57:43
【问题描述】:

我正在调查我的应用程序中的内存泄漏。这是上下文:

假设我必须处理不同类型的 XMLs 文件并且我每天收到大量的 XML 文件,所以我有一个IXmlProcessor 接口。

public interface IXmlProcessor
{
     void ProcessXml(string xml);
}

还有一些具体的 XMLProcessors。

public class UserXmlProcessor : IXmlProcessor
{
     private readonly IUserRepository _userRepository;

     public UserXmlProcessor(IUserRepository userRepository)
     {
           _userRepository = userRepository;
     }

     public void ProcessXml(string xml)
     {
           // do something with the xml
           // call _userRepository 
     }
 }

所有IXmlProcessor 具体类型都注册到 DI 容器,为了解决它们,我有一个 Factory 类,它也注册到 DI 容器,如下所示:

public class XmlProcessorFactory where TType : class
{
    private readonly IServiceProvider _serviceProvider;

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

    public IXmlProcessor GetImplementation(string identifier)
    {
        var type = FindType(identifier);

        return _serviceProvider.GetService(type) as IXmlProcessor;
    }

    private Type FindType(string identifier)
    {
        // do some reflection to find the type based on the identifier (UserXmlProcessor, for example)
        // don't worry, there's caching to avoid unecessary reflection
    }
}

在某些时候我把它们都称为:

public class WorkItem
{
    public string Identifier { get; set; }
    public string Xml { get; set; }
}

public class WorkingClass
{

    private readonly XmlProcessorFactory _xmlProcessorFactory;

    public WorkingClass(XmlProcessorFactory xmlProcessorFactory)
    {
        _xmlProcessorFactory = xmlProcessorFactory;
    }

    public void DoWork(WorkItem item)
    {
        var processor = _xmlProcessorFactory.GetImplementation(item.Identifier);
        processor.ProcessXml(item.Xml);
    }
}

IUserRepository 是一个简单的实现,带有实体框架上下文。

所以,问题出在这里:根据微软documentation

从容器解析的服务绝不应该由开发人员处理。

通过 DI 接收 IDisposable 依赖项不需要接收者自己实现 IDisposable。 IDisposable 依赖项的接收者不应对该依赖项调用 Dispose。

因此,如果我将 IUserRepository 注入控制器,那很好,容器将处理对象的处置以及 EF 上下文的处置,不需要 IDisposable。

但是我的 Xml 处理器呢?文档说:

不是由服务容器创建的服务

开发者负责处置服务。

避免使用服务定位器模式。例如,当您可以使用 DI 代替时,不要调用 GetService 来获取服务实例。 另一个需要避免的服务定位器变体是注入一个在运行时解决依赖关系的工厂。这两种做法都混合了控制反转策略。

还有_ = serviceProvider.GetRequiredService<ExampleDisposable>(); 是一种反模式。但正如您所见,我确实需要在运行时根据 XML 标识符解决依赖关系,我不想求助于 switch case。

所以:

  • IXmlProcessors 是否应该实现 IDisposable 并手动释放 IUserRepository?
  • 我是否也应该级联并让 IUserRepository 实现 IDisposable 以释放 EntityContext?
  • 如果是这样,如果将其注入控制器中,这不会影响服务生命周期吗?

【问题讨论】:

    标签: c# .net .net-core dependency-injection idisposable


    【解决方案1】:

    而且_ = serviceProvider.GetRequiredService<ExampleDisposable>(); 也是一种反模式。

    这个说法太简单了。调用GetRequiredService不是Composition Root调用Service Locator anti-pattern 的实现,因此没问题。当组合根之外调用时,它是服务定位器反模式的实现。打电话的最大缺点 GetRequiredService 仅在合成根之外使用时存在。

    IXmlProcessors 是否应该实现 IDisposable 并手动释放 IUserRepository?

    没有。 Microsoft 文档是正确的。当您的 IUserRepository 从容器中解析时,容器将确保它(或其依赖项)被处理掉。在 IUserRepository 的使用者中添加处置逻辑以处置存储库只会导致使用者不必要的复杂性。依赖项只会被释放两次。

    我是否也应该级联并让 IUserRepository 实现 IDisposable 以释放 EntityContext?

    没有。当EntityContext 由 DI 容器管理时,它会再次确保将其处理掉。因此,IUserRepository 实现应该实现处置只是为了确保 EntityContext 被处置。容器会这样做。

    如果是这样,如果将其注入控制器中,这不会影响服务生命周期吗?

    在消费者上实施IDisposable 的问题之一是这会在系统中产生涟漪效应。使低级依赖成为一次性的,将迫使您使依赖链中的所有消费者也都是一次性的。这不仅会导致消费者(不必要的)复杂性,还会迫使系统中的许多类进行更新。这也意味着需要为所有这些类添加测试。这将是违反开放/封闭原则的典型示例。

    请注意,使用默认的 .NET Core DI Container,很容易意外导致内存泄漏。当您直接从根容器解析一次性 Scoped 或 Transient 组件而不是从 IServiceScope 解析它们时,就会发生这种情况。特别是一次性 Transient 组件很讨厌,因为一开始它似乎可以工作(因为你总是得到一个新实例),但是这些一次性 Transient 将一直保持活动状态,直到 Container 本身被处理掉,这通常只会在应用程序关闭时发生。

    因此,请确保您始终从服务范围解析,而不是从根容器解析(除非您运行短期(控制台)应用程序)。

    【讨论】:

    • 谢谢!我想我现在发现了我的问题。该应用程序是一个 Windows 服务。我刚刚意识到我只在应用程序启动时创建了一次服务范围。所以这意味着只有在释放单个作用域时才会释放瞬态资源,也就是在应用程序关闭时,对吧?
    猜你喜欢
    • 1970-01-01
    • 2018-12-14
    • 1970-01-01
    • 2019-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-09
    • 1970-01-01
    相关资源
    最近更新 更多