c# - .NET 核心 - 依赖注入(inject)、工厂和 IDisposable

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

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

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

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

还有一些具体的 XMLProcessor。

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 容器,为了解析它们,我有一个工厂类,它也已注册到 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是一个简单的实现,带有 Entity Framework 上下文。

所以,这就是问题所在:根据 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 注入(inject) Controller ,那很好,容器将处理对象的处置以及 EF 上下文的处置,不需要 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 case。

所以:

  • IXmlProcessors 是否应该实现 IDisposable 并手动释放 IUserRepository?
  • 我是否也应该级联并使 IUserRepository 实现 IDisposable 以释放 EntityContext?
  • 如果是这样,如果将其注入(inject) Controller ,是否会影响使用生命周期?

最佳答案

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

这个说法太简单了。打电话GetRequiredService 不是 Service Locator anti-pattern 的实现当从 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 的问题之一对消费者来说,这会波及整个系统。使低级别的依赖项成为一次性的,将迫使您也使依赖链中的所有消费者成为一次性的。这不仅会导致消费者(不必要的)复杂性,还会迫使系统中的许多类进行更新。这也意味着需要为所有这些类添加测试。这将是违反开放/封闭原则的典型示例。

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

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

关于c# - .NET 核心 - 依赖注入(inject)、工厂和 IDisposable,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64913305/

相关文章:

c# - 如何从 Azure Devops 轻松调试内部 Nuget 包

c# - 指定的转换对泛型无效

C#,调试 HTTPHandler

c# - Newtonsoft Json 将值 {null} 转换为类型 'System.Int32' 时出错

.net - 如何折叠段落?

c# - 如何获取真正运行的进程名?

c# - 在 TabControl 中显示多种控件类型

c# - 如何消除重复代码

asp.net-core - .net core 在运行时加载 *.deps.json 以加载依赖程序集的正确版本

sql-server - Azure 上的 .NET Core 无法连接到 SQL Server 数据库