我误解的关键在于,我希望直接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
的Resolve会触发IEnvironment
的解析。因此,如果我使用OnActivated
,那么我们就没有解决问题,但是在调用构造函数之前,环境尚未初始化。
实际问题(最后!):
如上所述,目前正在发生的事情:
Resolve<IndependentClass>
触发.. Resolve<IEnvironment>
OnActivating<Environment>
触发Environment.Initialize
... SampleComponent.Initialize
... Resolve<IEnvironment>
.. Environment
因此即使我已将Environment
注册为单身,也会创建两个实例。
这不是一个错误,它似乎是预期的行为(因为Initialize
调用发生在OnActivating
并且实例尚未注册),但是我该怎么做才能解决这个问题?
我想要求:
当Resolve
为SampleComponent
时,环境Resolve
会在延迟的基础上发生。 (因为不总是需要环境。)
在将实例传递给Environment.Initialize
ctor之前进行SampleComponent
调用。
SampleComponent
不必采用ISimulator
ctor参数,因为通常不需要它。 (但我不反对将工厂模式重组为更加Autofac友好的东西,只要我不必要求我的(非顶级)组件能够识别Autofac。)
基本上,我只想要求在使用IEnvironment实例之前进行Initialization调用,并且由于Environment
/ SampleComponent
/ Simulator
对象图完全独立,这似乎应该能够连接/表达。
我尝试过的事情:
首先明确解决贸易环境:如上所述,这有效,但我觉得这个要求有点过于局限。主要是因为我有一些可选配置,我希望在构建容器之后(通过UI或其他)允许(但在环境解决之前),并且由于环境(或模拟器)并不总是需要,我在需要之前不要实例化它。 (同样适用于IStartable
或AutoActivate
,除非有其他方法可以使用它们,但我没有看到。)
放弃服务定位器模式。但是,在这种情况下,我需要表达SampleComponent
仅需要为某些serviceType值解析ISimulator
,否则将null传递给构造函数(或Property / etc)。是否有一种干净的表达方式?
最后,创建我自己的实例注册,并将Environment实例存储为静态单例。类似的东西:
builder.Register(c =&gt; CreateInstance())。AsSelf()。As()。SingleInstance()。OnActivating(e =&gt; e.Instance.Initialize());
其中:
private static Environment _globalInstance;
private static Environment CreateInstance()
{
if (_globalInstance == null)
{
_globalInstance = new Environment();
}
return _globalInstance;
}
但这样做有效:1。OnActivating
仍然需要为每个&#34;新&#34;实例。 2.感觉太过于苛刻 - 最终我现在正在管理实例和构造,这就是容器的用途。 (当你真的想要使用容器来解析参数时,它也会有点烦人,但是再次可以很容易地解决这个问题。)
所有这一切(我非常感谢你这么做),似乎我在这里有一个根本的误解。 (我猜它与SampleComponent
中的服务定位器模式和/或随意工厂有关,但我不会停止推测。)
我想真正的问题是:我缺少什么?
答案 0 :(得分:3)
尝试从示例中运行您的确切代码,我无法解析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();
}
}
}