对于 class A 和从属 class B 实例,每个字符串 ctor 参数值的单例

Singleton per string ctor parameter value, for both class A and dependent class B instances

我需要创建一个实例,不是基于类型或生命周期范围声明,而是基于构造函数的字符串参数的值。对于另一个 class 的依赖实例,我也需要同样的效果,在其自己的无关构造器中使用相同的字符串。

class A {
  public A(string appId, B b) {}
}
class B {
  public B(string appId) {}
}

在上面的示例中,我需要为 appId 的唯一值创建一个 A 单例和一个 B 单例。

我可以使用 TypedParameter 解析 A 和 B,但是我无法弄清楚每个 appId 值的单例部分。我只尝试了 A 来简化(不涉及依赖 B)。我查看了文档中的 Keyed、Indexed 等,但 none 似乎适合每个用户定义的键值单例,除非我编写自己的 Register lambda,它使用我自己的唯一 appId 内存缓存键。

Autofac 是否有内置的简洁方式来强制执行此操作?

这个问题不会有很好的解决方案,因为依赖注入通常是基于类型,而不是参数值。这与基于参数值为 Web 应用程序设置缓存不同。更具体地说,Autofac 没有任何可以帮助您的“内置”内容。 (据我所知,任何其他 DI 框架也没有。)

您可能需要做的是创建一个小型工厂来为您进行缓存并使用它。

这是一个例子,我没有编译也没有测试,但我突然想到让你畅通无阻。

首先,您需要为您的 B class 创建一个小工厂。

public class BFactory
{
  private ConcurrentDictionary<string, B> _cache = new ConcurrentDictionary<string, B>();

  public B GetB(string appId)
  {
    return this._cache.GetOrAdd(
      appId,
      a => new B(a));
  }
}

现在您将 BFactory 注册为单例,这将使您获得想要的 one-instance-per-app-ID 行为。将 B 注册为 lambda,它可以使用参数。

builder.RegisterType<BFactory>().SingleInstance();
builder.Register((c, p) =>
  {
    var appId = p.Named<string>("appId");
    var factory = c.Resolve<BFactory>();
    return factory.GetB(appId);
  });

现在你可以解析一个B 只要有一个参数传递给Resolve,比如

using var scope = container.BeginLifetimeScope();
var b = scope.Resolve<B>(new NamedParameter("appId", "my-app-id"));

您可以为 A 构建类似的东西,但是 AFactory 可以将 BFactory 作为参数,因此它可以获得 A 的正确实例。

public class AFactory
{
  private ConcurrentDictionary<string, B> _cache = new ConcurrentDictionary<string, B>();

  private readonly BFactory _factory;
  
  public AFactory(BFactory factory)
  {
    this._factory = factory;
  }

  public A GetA(string appId)
  {
    return this._cache.GetOrAdd(
      appId,
      a => new A(a, this._factory.GetB(a)));
  }
}

同样的事情,将工厂注册为单例并从工厂获取A

builder.RegisterType<AFactory>().SingleInstance();
builder.Register((c, p) =>
  {
    var appId = p.Named<string>("appId");
    var factory = c.Resolve<AFactory>();
    return factory.GetA(appId);
  });

你也可以喜欢它,比如如果有一些东西需要来自容器,就可以使用 Func relationships。例如,假设您的 B class 真的看起来像这样:

public class B
{
  public B(string appId, IComponent fromContainer) { /* ... */ }
}

在那里,也许 IComponent 需要来自容器,但 appId 来自参数。你可以让 BFactory 变成这样:

public class BFactory
{
  private ConcurrentDictionary<string, B> _cache = new ConcurrentDictionary<string, B>();

  private ILifetimeScope _scope;

  public BFactory(ILifetimeScope scope)
  {
    // This will be the CONTAINER / root scope if BFactory
    // is registered as a singleton!
    this._scope = scope;
  }

  public B GetB(string appId)
  {
    return this._cache.GetOrAdd(
      appId,
      a => {
        // Auto-generated factory! It will get IComponent from
        // the container but let you put the string in as a parameter.
        var func = this._scope.Resolve<Func<string, B>>();
        return func(a);
      });
  }
}

请注意,如果您使用 auto-generated 工厂事物(Func<string, B> 事物),您将需要自己注册 B,例如:

// You can't register the factory as the provider for B
// because it's cyclical - the BFactory will want to resolve
// a Func<string, B> which, in turn, will want to execute the
// BFactory.
builder.RegisterType<B>();
builder.RegisterType<BFactory>().SingleInstance();

这意味着您必须转换代码以使用 BFactory 而不是 B。您可能可以随意使用它来使其工作,但您明白了 - 您将不得不自己创建缓存机制,这就是您将挂钩到 Autofac 中的内容。希望上面的片段可以给你一些想法,你可以扩展并让你畅通无阻。