首页 > 解决方案 > AutoFac - 当组件解析为单例的依赖项时,InstancePerLifetimeScope 变为 SingleInstance

问题描述

原来的:

摘要: 我在使用 Autofac 时遇到问题,其中注册为单例/“SingleInstance”的对象的依赖项注册为“InstancePerLifetimeScope”,在解决第一个单例后最终变成“单例” .

详细信息:我正在开展一个项目,将 Autofac(版本 #4.9.2)合并到负责运行后台任务的 Windows 服务中。在准备过程中,我尝试阅读所有涉及生命周期范围的内容。我的代码具有 Autofac 解析服务对象,其中每个服务都有自己的预期角色和功能,和/或有时与其他服务交互(目前没有循环依赖)。为此,我将这些服务注册为单例,然后按顺序解析/启动每个服务。通过将服务注册为单例,如果另一个服务依赖于已解析的服务,而不是实例化一个新服务 Autofac 返回已解析的服务。

我遇到的问题是,在某些情况下,服务具有依赖关系,当解决时,预计依赖关系将在整个服务中重用(我的示例是记录器)。但是,还期望每个服务中已解析的依赖项应该不同,因为每个服务的已解析依赖项可能具有不同的行为。为此,我将这些依赖项注册为“InstancePerLifetimeScope”。然而,当服务被解析时,注册为“InstancePerLifetimeScope”的服务依赖最终会出现在 Autofac 的根容器中,该根容器会为共享相同依赖的所有其他服务检索,而不是将依赖解析为新对象。

示例:我在下面放置了一个简化示例,该示例类似于我的代码,它复制并概述了问题是如何受到影响的。

// Use of AutoFac with Established Components
var builder = new ContainerBuilder();

// Register
builder.RegisterType<Logger>().As<ILogger>().InstancePerLifetimeScope();
builder.RegisterType<Service>().Named<IService>("first").SingleInstance();
builder.RegisterType<Service>().Named<IService>("second").SingleInstance();

// Build
var container = builder.Build();

// Resolve
IService firstService = null;
using (ILifetimeScope firstScope = container.BeginLifetimeScope())
{
   firstService = firstScope.ResolveNamed<IService>("first");
};
IService secondService = null;
using (ILifetimeScope secondScope = container.BeginLifetimeScope())
{
   secondService = secondScope.ResolveNamed<IService>("second");
};

// Test

// This case is false, both services are different
if (firstService == secondService)
{
   throw new Exception("Services were the same");
}

// This case is true, each service shares the same logger
if (((Service)firstService)._logger == ((Service)secondService)._logger)
{
   throw new Exception("Loggers were the same");
}

public interface ILogger
{
   void Log(string message);
}

public interface IService
{
   void Run();
}

public class Logger : ILogger
{
   public Logger()
   {
      Console.WriteLine("Create: Logger");
   }

   public void Log(string message)
   {
      Console.WriteLine(message);
   }
}

public class Service : IService
{
   public readonly ILogger _logger;

   public Service(ILogger logger)
   {
      Console.WriteLine("Create: Service");
      _logger = logger;
   }

   public void Run()
   {
      Console.WriteLine("Run");
   }
}

结论:我希望当一个对象注册为“InstancePerLifetimeScope”时,无论它被解析的对象是否注册为“SingleInstance”,它都将始终绑定到当前子范围。但是,似乎已解决的依赖项被放置在根范围内。

我可以做些什么来强制“InstancePerLifetimeScope”注册依赖项与为“SingleInstance”解析的对象完全相同,因此这些依赖项不会最终出现在根容器中并被其他服务重用?

编辑 2019 年 8 月 5 日:

来自指向Captive Dependencies主题的John King链接,我已经审查了该主题并同意我的问题属于强制依赖的领域。然而,我对此事的处理方式感到惊讶,因为强制依赖只是作为所有 IOC 库中的一个已知问题存在,而且似乎几乎没有希望讨论不同的解决方案来缓解更新中的问题。尽管如此,我仍然认为我遇到的问题与 Autofac 文档中概述的示例以及 AutoFac 引用的有关此问题如何发生的其他资源有所不同。我对这个论点的推理是,我认为这里真正的问题源于“InstancePerLifetimeScope”,所以我想知道这是否保证了另一种解决方案的可能性。

Autofac 给出的示例和Mark Seemann 博客文章的链接(这是理解强制依赖的好资源,只是有一个不好的例子),尝试显示一个发生的单例的强制依赖,该单例具有注册为“实例”的依赖每个依赖项”。问题是这个例子(如下所示)在指出实际问题时是错误的。“ProductService”注册为单例,因此在第一次解析后,它将始终返回相同的“ProductService”引用,示例暗示的情况并非如此。

AutoFac Doc 链接的示例不正确

// Example from https://blog.ploeh.dk/2014/06/02/captive-dependency/
var builder = new ContainerBuilder();
builder.RegisterType<ProductService>().SingleInstance();
builder.RegisterType<SqlProductRepository>().As<IProductRepository>();
builder.RegisterType<CommerceContext>();
var container = builder.Build();

var actual1 = container.Resolve<ProductService>();
var actual2 = container.Resolve<ProductService>();

// You'd want this assertion to pass, but it fails
Assert.NotEqual(actual1.Repository, actual2.Repository);

// Lightbarrier snippet: Ya but guess what this also fails
Assert.NotEqual(actual1, actual2);

从上面的代码中可以看出,对于“每个依赖的实例”注册,可能会发生强制依赖,如下面的示例所示,情况并非如此。

实际发生的事情的例子

var builder = new ContainerBuilder();

// Register
builder.RegisterType<Logger>().As<ILogger>(); // InstancePerDependency
builder.RegisterType<Test.Service.Service1>().As<IService1>().SingleInstance();
builder.RegisterType<Test.Service.Service2>().As<IService2>().SingleInstance();

// Build
var container = builder.Build();

// Resolve
IService1 firstService = container.Resolve<IService1>();
IService2 secondService = container.Resolve<IService2>();

// This is false in that "InstancePerDependency" didn't result in a Captive Dependency
if (((Test.Service.Service1)firstService)._logger == ((Test.Service.Service2)secondService)._logger)
{
   throw new Exception("Loggers were the same");
}

这意味着只有当您尝试注册一个类的生命周期以驻留在它被解析的当前范围或更大/父范围内时,才会发生强制依赖。

其他人的建议是,仅将单例的依赖项按依赖项注册实例不会有很大的不同,但是,我认为这忽略了重点。在某些情况下,重用相同的依赖项很重要,我使用记录器作为一个主要示例,其中具有多个分辨率可能会导致争夺外部资源,例如记录数据的位置。更重要的是,大多数这些强制依赖问题都可以通过避免注册单例来避免,就好像您在子范围内的共享至少可以关闭孩子一样,但是对于单例,依赖关系被捕获在根目录中。然而我认为,如果我们不使用像 Autofac 这样的 IOC 库并自己手动传递它们,那么这些依赖关系可以按照我们的需要解决,而无需捕获依赖关系,所以从逻辑上讲,如果我们可以,AutoFac 应该可以做到这一点。但是,我希望 Autofac 为我完成这项工作,但这是一个很大的陷阱,我认为大多数人在查看 IOC 库时应该意识到这一点。

问题: 我读过更新 Autofac 以解决共享注册组件的这个问题很难编程,但 Autofac 真的很难知道注册为跨范围共享的单例依赖项应该驻留在仅表示当前范围,而不是 Singleton 所在的根范围?我敢说,当一个单例实例被解析时,实例的依赖项不需要驻留在根范围内以使单例实例存在,因为它们将由于它们在单例实例中的引用而存在于内存中,因此没有根范围保存这些依赖项的原因。

如果情况确实如此,唯一的途径似乎是永远不要在 AutoFac 中使用 Singleton 功能,并找到另一种解决方法。也就是说,我认为AutoFac 文档的 Singleton 部分中应该有一个关于这个问题的巨大警告,目前缺少该部分。

标签: c#singletonautofaclifetime-scoping

解决方案


推荐阅读