【发布时间】:2015-06-18 18:13:23
【问题描述】:
我误解的症结在于,我希望直接 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 并且该实例尚未注册),但我应该怎么做才能解决这个问题?
我想要求:
当
SampleComponent是Resolve时,环境Resolve会延迟发生。 (因为并不总是需要环境。)Environment.Initialize调用是在实例传递给SampleComponentctor 之前进行的。SampleComponent不必采用ISimulatorctor 参数,因为它通常不是必需的。 (但我不会反对将工厂模式重组为更适合 Autofac 的东西,只要我不必要求我的(非顶级)组件支持 Autofac。)
基本上,我只想要求在使用 IEnvironment 实例之前进行初始化调用,并且由于 Environment/SampleComponent/Simulator 对象图是完全独立的,这似乎应该能够接线/表达。
我尝试过的事情:
首先明确解决贸易环境:如前所述,这是可行的,但我觉得这个要求有点太受限制了。主要是因为我喜欢在构建容器之后(但在解析环境之前)允许一些可选配置(通过 UI 或其他方式),并且由于并不总是需要环境(或模拟器),所以我不需要不想实例化它,直到需要它。 (
IStartable或AutoActivate也是如此,除非我没有看到其他使用它们的方法。)放弃服务定位器模式。不过,在这种情况下,我需要表示
SampleComponent需要为 serviceType 的某些值解析ISimulator,否则将 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.
OnActivating仍然为每个“新”实例调用。 2. 感觉太老套了——最终我现在正在管理实例和构造,这就是容器的用途。 (当你真的想使用容器来解析参数时,这也有点烦人,但同样可以很容易地解决。)
说了这么多(我非常感谢你能做到这一点),看来我在这里有一个根本的误解。 (我猜它与SampleComponent 中的服务定位器模式和/或随意工厂有关,但我将停止推测。)
我想真正的问题是:我错过了什么?
【问题讨论】:
-
看起来你有一个被服务位置掩盖的循环引用。
Environment=>SampleComponent=>Simulator=>Environment。如果你在Environment中加了一个锁(如果你不使用静态构造函数,我建议单例初始化)你会死锁。你应该放松一下,看看你是否还有问题。 -
@Travis - 同意存在循环依赖,虽然作为最佳实践当然不鼓励这样做,但它是 supported。也就是说,构造/注入中的依赖关系 - Initialize 调用应该在注入后发生并且不应该有任何问题,除了在触发 OnActivating 之前没有注册实例的事实之外。 (同意锁,实际上我总是会使用它。但它不会死锁,因为我相信不会在锁内调用 Initialize。)
-
我知道它是受支持的(我编写了文档),但它在以构造函数或注入属性表示的依赖关系的上下文中受支持 - 而不是您在运行服务定位器时破坏链的方式在中间。我很快就会发布一个更大的答案。
-
“我写了文档”——这很难反驳!好的,我肯定很期待,在此期间会考虑一下。感谢您的洞察力先生!
标签: c# design-patterns inversion-of-control autofac