我的误解的症结在于,我想针对相同的单例类型,直接将Resolve()作为OnActivating事件的结果而调用的嵌套方法中的类型,用于同一单例类型,而autofac试图创建该单例的第二个实例。

长得多的版本:

首先是一个完整的示例,然后我将进行总结:

public static class AutofacTest
{
    public static void Test()
    {
        var builder = new ContainerBuilder();

        // Register the environment as a singleton, and call Initialize when created
        builder.RegisterType<Environment>().AsSelf().As<IEnvironment>().SingleInstance().OnActivating(e => e.Instance.Initialize());

        // Register the simulator, also a singleton and dependent on
        builder.RegisterType<Simulator>().AsSelf().As<ISimulator>().SingleInstance();

        // Register a simple class, that needs an initialized environment
        builder.RegisterType<IndependentClass>();

        // Build/scope
        var context = builder.Build();
        var scope = context.BeginLifetimeScope();

        // Register the service locator
        ServiceLocator.GlobalScope = scope;

        //var childScope = scope.BeginLifetimeScope(cb =>
        //{
        //    cb.RegisterType<IndependentClass>();
        //});

        // Now resolve the independent class, which will trigger the environment/simulator instantiation
        var inst = scope.Resolve<IndependentClass>();
    }
}

public static class ServiceLocator
{
    public static ILifetimeScope GlobalScope { get; set; }
}

public interface IEnvironment
{
    bool IsInitialized { get; }
}

public class Environment : IEnvironment
{
    private static Environment Instance;

    private SampleComponent _component;
    private bool _isInitialized;

    public bool IsInitialized
    {
        get { return _isInitialized; }
    }

    public void Initialize()
    {
        if (Instance != null) throw new InvalidOperationException();
        Instance = this;

        // Canonical complex code which forces me into what I think is a tricky situation...

        _component = new SampleComponent(SampleServiceType.SimulatedThing);

        _component.Initialize();

        _isInitialized = true;
    }
}

public interface ISimulator { }

public class Simulator : ISimulator
{
    private static Simulator Instance;

    private readonly IEnvironment _environment;

    public Simulator(IEnvironment environment)
    {
        if (Instance != null) throw new InvalidOperationException();
        Instance = this;

        _environment = environment;
    }
}

public enum SampleServiceType
{
    None = 0,
    RealThing,
    SimulatedThing,
}

public class SampleComponent
{
    private readonly SampleServiceType _serviceType;

    public SampleComponent(SampleServiceType serviceType)
    {
        _serviceType = serviceType;
    }

    public void Initialize()
    {
        // Sample component that has different types of repositories
        switch (_serviceType)
        {
            case SampleServiceType.SimulatedThing:
                var sim = ServiceLocator.GlobalScope.Resolve<ISimulator>();
                // Create a repositiry object or something requriing the simulator
                break;
        }
    }
}

public class IndependentClass
{
    public IndependentClass(IEnvironment env)
    {
        if (!env.IsInitialized) throw new InvalidOperationException();
    }
}


所以关键点:


Environment是顶级容器,模拟器取决于环境,环境的组成部分(SampleComponent)取决于环境和模拟器。
至关重要的是,组件并不总是使用模拟器(这并不罕见),因此这里有一个工厂样式模式的地方。在这种情况下,我通常会使用全局服务定位器(而且我相信我理解为什么这可能是邪恶的,并且很可能在这里咬我)-但主要原因是恰恰是针对诸如模拟器之类的东西(或者通常是针对UI用途),我不想在构造函数中依赖模拟器,因为它仅在某些情况下使用。 (下面有更多。)
创建后应初始化环境。因此,在这里使用OnActivating效果很好,除了一个警告以外...
IndependentClass需要一个IEnvironment,此时我需要一个完全初始化的IEnvironment。但是,在这种情况下,IndependentClass的解析是触发IEnvironment的解析的原因。因此,如果我使用OnActivated,那么就没有解决问题的方法,但是直到构造函数被调用后,环境才被初始化。


实际问题(最终!):

如所写,当前发生的情况:


Resolve<IndependentClass>触发器
Resolve<IEnvironment>
OnActivating<Environment>触发Environment.Initialize ...
然后调用SampleComponent.Initialize ...
哪个称为全局作用域Resolve<IEnvironment>
然后解析/实例化第二个Environment


因此,即使我将Environment注册为单例,也会创建两个实例。

这不是错误,它似乎是预期的行为(因为Initialize调用在OnActivating中发生,并且该实例尚未注册),但是我应该怎么做才能解决此问题?

我想要求:


Resolve被添加后,环境SampleComponent会延迟发生。 (因为并非总是需要环境。)
在实例传递给Resolve ctor之前先进行Environment.Initialize调用。
SampleComponent不必采用SampleComponent ctor参数,因为通常不需要。 (但是,只要我不需要我的(非顶级)组件必须了解Autofac,我就不会反对将工厂模式重组为对Autofac更友好的东西。)


基本上,我只想要求在使用IEnvironment实例之前进行初始化调用,并且由于ISimulator / Environment / SampleComponent对象图是完全独立的,因此似乎应该能够有线/表达。

我尝试过的事情:


首先明确解决贸易环境:如上所述,这行得通,但是我发现要求有点过于严格。主要是因为我有一些可选的配置,我希望在构建容器之后(但在解决环境之前)(通过UI或其他方式)允许这些配置,并且由于并非总是需要环境(或模拟器),因此我不需要不想实例化它,直到需要它。 (对于SimulatorIStartable也是如此,除非我没有看到使用它们的另一种方法。)
放弃服务定位器模式。但是在那种情况下,我需要表达AutoActivate仅需要为serviceType的某些值解析SampleComponent,否则将null传递给构造函数(或Property / etc)。有没有一种清晰的表达方式?
最后,创建我自己的实例注册,并将Environment实例存储为静态单例。就像是:

builder.Register(c => CreateInstance())。AsSelf()。As()。SingleInstance()。OnActivating(e => e.Instance.Initialize());

哪里:

    private static Environment _globalInstance;
private static Environment CreateInstance()
{
    if (_globalInstance == null)
    {
        _globalInstance = new Environment();
    }
    return _globalInstance;
}


但是,该方法有效:1.仍为每个“新”实例调用ISimulator。 2.感觉太hacky-最终,我现在要管理实例和构造,这就是容器的用途。 (当您实际上想使用容器来解析参数时,这也有些烦人,但是再次可以很容易地解决它。)


因此,所有这些(我非常感谢您到目前为止所做的一切),似乎在这里我有一个基本的误解。 (我猜想它与服务定位器模式和/或OnActivating中的偶然工厂有关,但我不再猜测。)

我想真正的问题是:我想念什么?

最佳答案

尝试从示例中运行您的确切代码,我无法解析IndependentClass,因为我(正确)获得了异常。异常堆栈看起来像一个循环依赖项,在该循环中,它嵌套,嵌套并嵌套相同的异常,例如堆栈溢出:

Autofac.Core.DependencyResolutionException was unhandled
  _HResult=-2146233088
  _message=An exception was thrown while executing a resolve operation. See the InnerException for details.
  HResult=-2146233088
  IsTransient=false
  Message=An exception was thrown while executing a resolve operation. See the InnerException for details. ---> Operation is not valid due to the current state of the object. (See inner exception for details.)
  Source=Autofac
  StackTrace:
       at Autofac.Core.Resolving.ResolveOperation.Execute(IComponentRegistration registration, IEnumerable`1 parameters)
       at Autofac.Core.Lifetime.LifetimeScope.ResolveComponent(IComponentRegistration registration, IEnumerable`1 parameters)
       at Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable`1 parameters, Object& instance)
       at Autofac.ResolutionExtensions.ResolveService(IComponentContext context, Service service, IEnumerable`1 parameters)
       at Autofac.ResolutionExtensions.Resolve(IComponentContext context, Type serviceType, IEnumerable`1 parameters)
       at Autofac.ResolutionExtensions.Resolve[TService](IComponentContext context, IEnumerable`1 parameters)
       at Autofac.ResolutionExtensions.Resolve[TService](IComponentContext context)
       at SingletonRepro.SampleComponent.Initialize() in e:\dev\spike\SingletonRepro\SingletonRepro\Program.cs:line 120
       at SingletonRepro.Environment.Initialize() in e:\dev\spike\SingletonRepro\SingletonRepro\Program.cs:line 75
       at SingletonRepro.Program.<Main>b__0(IActivatingEventArgs`1 e) in e:\dev\spike\SingletonRepro\SingletonRepro\Program.cs:line 17
       at Autofac.Builder.RegistrationBuilder`3.<>c__DisplayClass6.<OnActivating>b__5(Object s, ActivatingEventArgs`1 e)
       at Autofac.Core.Registration.ComponentRegistration.RaiseActivating(IComponentContext context, IEnumerable`1 parameters, Object& instance)
       at Autofac.Core.Resolving.InstanceLookup.Activate(IEnumerable`1 parameters)
       at Autofac.Core.Resolving.InstanceLookup.<Execute>b__0()
       at Autofac.Core.Lifetime.LifetimeScope.GetOrCreateAndShare(Guid id, Func`1 creator)
       at Autofac.Core.Resolving.InstanceLookup.Execute()
       at Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, IComponentRegistration registration, IEnumerable`1 parameters)
       at Autofac.Core.Resolving.InstanceLookup.ResolveComponent(IComponentRegistration registration, IEnumerable`1 parameters)
       at Autofac.Core.Activators.Reflection.AutowiringParameter.<>c__DisplayClass2.<CanSupplyValue>b__0()
       at Autofac.Core.Activators.Reflection.ConstructorParameterBinding.Instantiate()
       at Autofac.Core.Activators.Reflection.ReflectionActivator.ActivateInstance(IComponentContext context, IEnumerable`1 parameters)
       at Autofac.Core.Resolving.InstanceLookup.Activate(IEnumerable`1 parameters)
       at Autofac.Core.Resolving.InstanceLookup.Execute()
       at Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, IComponentRegistration registration, IEnumerable`1 parameters)
       at Autofac.Core.Resolving.ResolveOperation.ResolveComponent(IComponentRegistration registration, IEnumerable`1 parameters)
       at Autofac.Core.Resolving.ResolveOperation.Execute(IComponentRegistration registration, IEnumerable`1 parameters)
       at Autofac.Core.Lifetime.LifetimeScope.ResolveComponent(IComponentRegistration registration, IEnumerable`1 parameters)
       at Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable`1 parameters, Object& instance)
       at Autofac.ResolutionExtensions.ResolveService(IComponentContext context, Service service, IEnumerable`1 parameters)
       at Autofac.ResolutionExtensions.Resolve(IComponentContext context, Type serviceType, IEnumerable`1 parameters)
       at Autofac.ResolutionExtensions.Resolve[TService](IComponentContext context, IEnumerable`1 parameters)
       at Autofac.ResolutionExtensions.Resolve[TService](IComponentContext context)
       at SingletonRepro.Program.Main(String[] args) in e:\dev\spike\SingletonRepro\SingletonRepro\Program.cs:line 38
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException: System.InvalidOperationException
       _HResult=-2146233079
       _message=Operation is not valid due to the current state of the object.
       HResult=-2146233079
       IsTransient=false
       Message=Operation is not valid due to the current state of the object.
       Source=SingletonRepro
       StackTrace:
            at SingletonRepro.Environment.Initialize() in e:\dev\spike\SingletonRepro\SingletonRepro\Program.cs:line 68
            at SingletonRepro.Program.<Main>b__0(IActivatingEventArgs`1 e) in e:\dev\spike\SingletonRepro\SingletonRepro\Program.cs:line 17
            at Autofac.Builder.RegistrationBuilder`3.<>c__DisplayClass6.<OnActivating>b__5(Object s, ActivatingEventArgs`1 e)
            at Autofac.Core.Registration.ComponentRegistration.RaiseActivating(IComponentContext context, IEnumerable`1 parameters, Object& instance)
            at Autofac.Core.Resolving.InstanceLookup.Activate(IEnumerable`1 parameters)
            at Autofac.Core.Resolving.InstanceLookup.<Execute>b__0()
            at Autofac.Core.Lifetime.LifetimeScope.GetOrCreateAndShare(Guid id, Func`1 creator)
            at Autofac.Core.Resolving.InstanceLookup.Execute()
            at Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, IComponentRegistration registration, IEnumerable`1 parameters)
            at Autofac.Core.Resolving.InstanceLookup.ResolveComponent(IComponentRegistration registration, IEnumerable`1 parameters)
            at Autofac.Core.Activators.Reflection.AutowiringParameter.<>c__DisplayClass2.<CanSupplyValue>b__0()
            at Autofac.Core.Activators.Reflection.ConstructorParameterBinding.Instantiate()
            at Autofac.Core.Activators.Reflection.ReflectionActivator.ActivateInstance(IComponentContext context, IEnumerable`1 parameters)
            at Autofac.Core.Resolving.InstanceLookup.Activate(IEnumerable`1 parameters)
            at Autofac.Core.Resolving.InstanceLookup.<Execute>b__0()
            at Autofac.Core.Lifetime.LifetimeScope.GetOrCreateAndShare(Guid id, Func`1 creator)
            at Autofac.Core.Resolving.InstanceLookup.Execute()
            at Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, IComponentRegistration registration, IEnumerable`1 parameters)
            at Autofac.Core.Resolving.ResolveOperation.ResolveComponent(IComponentRegistration registration, IEnumerable`1 parameters)
            at Autofac.Core.Resolving.ResolveOperation.Execute(IComponentRegistration registration, IEnumerable`1 parameters)
       InnerException: ...


在对问题的评论中,您正确地指出了Autofac does support circular dependencies。是的,但是在单个解析周期的情况下,这是正确的。这里的问题是,通过在中间添加服务位置,特别是在SampleComponent.Initialize方法中,已拆分了单个解析链。

不管您如何堆叠它-问题是您是否以某种方式获取了两个单例,还是遇到了此异常-都归结为需要打破这种循环依赖关系。

如果绝对必须使用服务位置,则打破依赖关系的一种方法是使用the Lazy<T> relationship。这是为您提供延迟的组件分辨率。在您的SampleComponent.Initialize方法中,将服务位置方法更改为如下所示:

var sim = ServiceLocator.GlobalScope.Resolve<Lazy<ISimulator>>();


如果您创建需要ISimulator的存储库,请尝试更改该存储库的构造函数以使用Lazy<ISimulator>并仅在最后可能的时刻调用Lazy<ISimulator>.Value。这将使Environment的解析操作延迟足够长的时间,以使整个链第一次正确完成,并使您摆脱该循环解析问题。

更好的选择是重构使用DI的方式。现在,您正在通过代码混合各种依赖注入,服务位置和手动实例构建。 Environment手动创建一个SampleComponent; SampleComponent使用服务位置获取ISimulatorISimulator使用DI来获取IEnvironment。这样的混合和匹配将导致您遇到各种麻烦,就像现在看到的那样。

实际上,完全使用DI意味着您实际上不需要在任何地方实现单例模式-只需使用构造函数并根据需要注册SingleInstance即可。

这是您的代码的更新版本(以控制台应用程序形式),其中显示了一些可能的操作思路。显然,您的实际代码可能更复杂,因此我无法从字面上向您展示每种情况的所有可能解决方案,但这是打破这种局面的一种方法。您可以从此处和其他available implicit relationship types中利用想法来解决挑战。

using System;
using Autofac;
using Autofac.Features.Indexed;

namespace SingletonRepro
{
  class Program
  {
    static void Main()
    {
      var builder = new ContainerBuilder();

      // You can still keep the Initialize call if you want.
      builder.RegisterType<Environment>().As<IEnvironment>().SingleInstance().OnActivated(args => args.Instance.Initialize());

      // Everything's in DI now, not just some things.
      builder.RegisterType<Simulator>().As<ISimulator>().SingleInstance();
      builder.RegisterType<IndependentClass>();

      // Using keyed services to choose the repository rather than newing things up.
      builder.RegisterType<RealRepository>().Keyed<IRepository>(SampleServiceType.RealThing);
      builder.RegisterType<SimulatedRepository>().Keyed<IRepository>(SampleServiceType.SimulatedThing);
      builder.RegisterType<SampleComponent>().WithParameter("serviceType", SampleServiceType.SimulatedThing);

      var context = builder.Build();
      using (var scope = context.BeginLifetimeScope())
      {
        // Using Lazy<T> in the IndependentClass to defer the need for
        // IEnvironment right away - breaks the dependency circle.
        var inst = scope.Resolve<IndependentClass>();
        inst.DoWork();
        Console.WriteLine("Instance: {0}", inst);
      }
    }
  }

  public interface IEnvironment
  {
    bool IsInitialized { get; }
  }

  public class Environment : IEnvironment
  {
    public SampleComponent _component;

    public Environment(SampleComponent component)
    {
      this._component = component;
    }

    public void Initialize()
    {
      this._component.DoSomethingWithRepo();
      this.IsInitialized = true;
    }

    public bool IsInitialized { get; private set; }
  }

  public interface ISimulator
  {
  }

  public class Simulator : ISimulator
  {
    public Simulator(IEnvironment environment)
    {
      this.Environment = environment;
    }
    public IEnvironment Environment { get; private set; }
  }

  public enum SampleServiceType
  {
    None = 0,
    RealThing,
    SimulatedThing,
  }

  public class SampleComponent
  {
    private IIndex<SampleServiceType, IRepository> _repositories;

    private SampleServiceType _serviceType;

    // Use indexed/keyed services to pick the right one from a dictionary
    // rather than newing up the repository (or whatever) manually.
    public SampleComponent(IIndex<SampleServiceType, IRepository> repositories, SampleServiceType serviceType)
    {
      this._repositories = repositories;
      this._serviceType = serviceType;
    }

    public void DoSomethingWithRepo()
    {
      // You could always take the service type parameter in this function
      // rather than as a constructor param.
      var repo = this._repositories[this._serviceType];
      repo.DoWork();
    }
  }

  public interface IRepository
  {
    void DoWork();
  }

  public class SimulatedRepository : IRepository
  {
    private ISimulator _simulator;

    public SimulatedRepository(ISimulator simulator)
    {
      this._simulator = simulator;
    }

    public void DoWork()
    {
    }
  }

  public class RealRepository : IRepository
  {
    public void DoWork()
    {
    }
  }

  public class IndependentClass
  {
    private Lazy<IEnvironment> _env;

    // Delaying the need for the IEnvironment in the constructor
    // can help break the circular dependency chain, as well as not
    // immediately checking that it's initialized. (Can you just
    // TRUST that it's initialized and call it good?)
    public IndependentClass(Lazy<IEnvironment> env)
    {
      this._env = env;
    }

    public void DoWork()
    {
      if (!this._env.Value.IsInitialized)
        throw new InvalidOperationException();
    }
  }
}

10-04 15:13