.NET 核心 - 依赖注入、工厂和 IDisposable

.NET core - Dependency Injection, factories and IDisposable

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

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

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

还有一些具体的XML处理器。

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 容器,为了解决它们,我有一个工厂 class,它也注册到 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 是一个简单的实现,带有实体框架上下文。

所以,这就是问题所在:根据 Microsoft documentation:

Services resolved from the container should never be disposed by the developer.

Receiving an IDisposable dependency via DI doesn't require that the receiver implement IDisposable itself. The receiver of the IDisposable dependency shouldn't call Dispose on that dependency.

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

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

Services not created by the service container

The developer is responsible for disposing the services.

Avoid using the service locator pattern. For example, don't invoke GetService to obtain a service instance when you can use DI instead. Another service locator variation to avoid is injecting a factory that resolves dependencies at runtime. Both of these practices mix Inversion of Control strategies.

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

所以:

And also _ = serviceProvider.GetRequiredService<ExampleDisposable>(); being an anti-pattern.

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

Should the IXmlProcessors implement IDisposable and release IUserRepository manually?

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

Should I also cascade and make IUserRepository implement IDisposable to release EntityContext?

没有。当 EntityContext 由 DI 容器管理时,它将再次确保它被处理掉。所以 IUserRepository 实现应该 而不是 实现处置只是为了确保 EntityContext 被处置。容器将执行此操作。

If so, wouldn't that affect the service lifetime if it's injected in a controller?

对消费者实施 IDisposable 的问题之一是这会波及整个系统。使低级别的依赖项成为一次性的,将迫使您也使依赖链中的所有消费者成为一次性的。这不仅会导致消费者(不必要的)复杂性,还会迫使系统中的许多 类 进行更新。这也意味着需要为所有这些添加测试 类。这将是违反 Open/Closed 原则的典型示例。

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

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