我确信代码可以通过很多方式调用 SynchronizedLifetimeManager 或像 ContainerControlledLifetimeManager 这样的后代,但是有两种情况特别给我带来了问题。
第一个是我自己的错误 - 我使用构造函数注入来提供对容器的引用,并且在该构造函数中我还将该类的新实例添加到容器中以供将来使用。这种向后方法的效果是将生命周期管理器从 Transient 更改为 ContainerControlled,以便 Unity 调用 GetValue 的对象与其调用 SetValue 的对象不同。吸取的教训是在构建过程中不要做任何可能改变对象生命周期管理器的事情。
第二种情况是,每次调用RegisterInstance时,UnityDefaultBehaviorExtension都会调用SetValue,而不先调用GetValue。幸运的是,Unity 具有足够的可扩展性,只要有足够的热心,您就可以解决这个问题。
从像这样的新行为扩展开始:
/// <summary>
/// Replaces <see cref="UnityDefaultBehaviorExtension"/> to eliminate
/// <see cref="SynchronizationLockException"/> exceptions that would otherwise occur
/// when using <c>RegisterInstance</c>.
/// </summary>
public class UnitySafeBehaviorExtension : UnityDefaultBehaviorExtension
{
/// <summary>
/// Adds this extension's behavior to the container.
/// </summary>
protected override void Initialize()
{
Context.RegisteringInstance += PreRegisteringInstance;
base.Initialize();
}
/// <summary>
/// Handles the <see cref="ExtensionContext.RegisteringInstance"/> event by
/// ensuring that, if the lifetime manager is a
/// <see cref="SynchronizedLifetimeManager"/> that its
/// <see cref="SynchronizedLifetimeManager.GetValue"/> method has been called.
/// </summary>
/// <param name="sender">The object responsible for raising the event.</param>
/// <param name="e">A <see cref="RegisterInstanceEventArgs"/> containing the
/// event's data.</param>
private void PreRegisteringInstance(object sender, RegisterInstanceEventArgs e)
{
if (e.LifetimeManager is SynchronizedLifetimeManager)
{
e.LifetimeManager.GetValue();
}
}
}
那么您需要一种方法来替换默认行为。 Unity 没有删除特定扩展的方法,因此您必须删除所有内容并再次将其他扩展放回原处:
public static IUnityContainer InstallCoreExtensions(this IUnityContainer container)
{
container.RemoveAllExtensions();
container.AddExtension(new UnityClearBuildPlanStrategies());
container.AddExtension(new UnitySafeBehaviorExtension());
#pragma warning disable 612,618 // Marked as obsolete, but Unity still uses it internally.
container.AddExtension(new InjectedMembers());
#pragma warning restore 612,618
container.AddExtension(new UnityDefaultStrategiesExtension());
return container;
}
请注意UnityClearBuildPlanStrategies
? RemoveAllExtensions 清除容器的所有内部策略和策略列表(除了一个),因此我必须使用另一个扩展来避免在恢复默认扩展时插入重复项:
/// <summary>
/// Implements a <see cref="UnityContainerExtension"/> that clears the list of
/// build plan strategies held by the container.
/// </summary>
public class UnityClearBuildPlanStrategies : UnityContainerExtension
{
protected override void Initialize()
{
Context.BuildPlanStrategies.Clear();
}
}
现在您可以安全地使用RegisterInstance,而不必担心被逼到疯狂的边缘。为了确定起见,这里有一些测试:
[TestClass]
public class UnitySafeBehaviorExtensionTests : ITest
{
private IUnityContainer Container;
private List<Exception> FirstChanceExceptions;
[TestInitialize]
public void TestInitialize()
{
Container = new UnityContainer();
FirstChanceExceptions = new List<Exception>();
AppDomain.CurrentDomain.FirstChanceException += FirstChanceExceptionRaised;
}
[TestCleanup]
public void TestCleanup()
{
AppDomain.CurrentDomain.FirstChanceException -= FirstChanceExceptionRaised;
}
private void FirstChanceExceptionRaised(object sender, FirstChanceExceptionEventArgs e)
{
FirstChanceExceptions.Add(e.Exception);
}
/// <summary>
/// Tests that the default behavior of <c>UnityContainer</c> leads to a <c>SynchronizationLockException</c>
/// being throw on <c>RegisterInstance</c>.
/// </summary>
[TestMethod]
public void UnityDefaultBehaviorRaisesExceptionOnRegisterInstance()
{
Container.RegisterInstance<ITest>(this);
Assert.AreEqual(1, FirstChanceExceptions.Count);
Assert.IsInstanceOfType(FirstChanceExceptions[0], typeof(SynchronizationLockException));
}
/// <summary>
/// Tests that <c>UnitySafeBehaviorExtension</c> protects against <c>SynchronizationLockException</c>s being
/// thrown during calls to <c>RegisterInstance</c>.
/// </summary>
[TestMethod]
public void SafeBehaviorPreventsExceptionOnRegisterInstance()
{
Container.RemoveAllExtensions();
Container.AddExtension(new UnitySafeBehaviorExtension());
Container.AddExtension(new InjectedMembers());
Container.AddExtension(new UnityDefaultStrategiesExtension());
Container.RegisterInstance<ITest>(this);
Assert.AreEqual(0, FirstChanceExceptions.Count);
}
}
public interface ITest { }