c# - .NET 核心 - 依赖注入、工厂和 IDisposable
问题描述
我正在调查我的应用程序中的内存泄漏。这是上下文:
假设我必须处理不同类型的 XMLs 文件,并且每天收到大量的 XML 文件,所以我有一个IXmlProcessor
interface 。
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
是一个简单的实现,带有实体框架上下文。
所以,这就是问题所在:根据 Microsoft文档:
从容器解析的服务不应该由开发人员处理。
通过 DI 接收 IDisposable 依赖项不需要接收者自己实现 IDisposable。IDisposable 依赖项的接收者不应对该依赖项调用 Dispose。
因此,如果我将 IUserRepository 注入控制器,那很好,容器将处理对象的处置以及 EF 上下文的处置,不需要 IDisposable。
但是我的 Xml 处理器呢?文档说:
不是由服务容器创建的服务
开发人员负责处置服务。
避免使用服务定位器模式。例如,当您可以使用 DI 代替时,不要调用 GetService 来获取服务实例。另一个需要避免的服务定位器变体是注入一个在运行时解决依赖关系的工厂。这两种做法都混合了控制反转策略。
而且也是_ = serviceProvider.GetRequiredService<ExampleDisposable>();
一种反模式。但是正如您所看到的,我确实需要在运行时根据 XML 标识符解决依赖关系,并且我不想诉诸切换案例。
所以:
- IXmlProcessors 是否应该手动实现 IDisposable 并发布 IUserRepository?
- 我是否还应该级联并使 IUserRepository 实现 IDisposable 以释放 EntityContext?
- 如果是这样,如果将其注入控制器中,这不会影响服务生命周期吗?
解决方案
而且也是
_ = serviceProvider.GetRequiredService<ExampleDisposable>();
一种反模式。
这个说法太简单了。从Composition Root中调用时,调用GetRequiredService
不是Service Locator 反模式的实现,因此很好。在组合根外部调用时,它是服务定位器反模式的实现。调用的大多数缺点
只有在组合根之外使用时才存在。GetRequiredService
IXmlProcessors 是否应该手动实现 IDisposable 并发布 IUserRepository?
不,Microsoft 文档是正确的。当您的 IUserRepository 从容器中解析时,容器将确保它(或其依赖项)被处理掉。在 IUserRepository 的使用者中添加处置逻辑以处置存储库只会导致使用者不必要的复杂性。依赖项只会被释放两次。
我是否还应该级联并使 IUserRepository 实现 IDisposable 以释放 EntityContext?
不会。当EntityContext
DI Container 再次管理它时,它会确保它被处理掉。所以 IUserRepository 实现不应该仅仅为了确保被处理而实现处理EntityContext
。容器将执行此操作。
如果是这样,如果将其注入控制器中,这不会影响服务生命周期吗?
在消费者身上实施的问题之一IDisposable
是这会波及整个系统。使低级依赖成为一次性的,将迫使您使依赖链中的所有消费者也都是一次性的。这不仅会导致消费者(不必要的)复杂性,还会迫使系统中的许多类进行更新。这也意味着需要为所有这些类添加测试。这将是违反开放/封闭原则的典型示例。
请注意,使用默认的 .NET Core DI 容器,很容易意外导致内存泄漏。当您直接从根容器解析一次性 Scoped 或 Transient 组件而不是从IServiceScope
. 特别是一次性 Transient 组件很讨厌,因为起初它似乎可以工作(因为您总是得到一个新实例),但是这些一次性 Transient 将一直保持活动状态,直到 Container 本身被处理掉,这通常只会在应用程序关闭时发生。
因此,请确保您始终从服务范围解析,而不是从根容器解析(除非您运行短期(控制台)应用程序)。
推荐阅读
- python - 具有匹配 id 的行之间的成对距离
- javascript - $scope.Array 未在视图 ng-repeat 上更新
- css - 在 SASS 编译中为背景颜色保留 Alpha
- apache-kafka - Cygnus:无法传递事件。事务打开时调用 close() - 您必须先提交或回滚
- python - python烧瓶和pyrebase中的Seek()错误
- python - 在 Flask 中运行 discord.py 异步函数
- redirect - 如何将所有子域重定向到主页
- redis - 将 redis.conf 文件移动到 AMI 时,Packer 给我 SCP 权限被拒绝
- numpy - ValueError:检查输入时出错:预期 conv2d_16_input 有 4 个维度
- python - 使用 TensorFlow 进行多元线性回归