我的误解的症结在于,我想针对相同的单例类型,直接将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或其他方式)允许这些配置,并且由于并非总是需要环境(或模拟器),因此我不需要不想实例化它,直到需要它。 (对于
Simulator
或IStartable
也是如此,除非我没有看到使用它们的另一种方法。)放弃服务定位器模式。但是在那种情况下,我需要表达
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
使用服务位置获取ISimulator
; ISimulator
使用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();
}
}
}