Je suis sûr qu'il ya beaucoup de façons de code pourrait appeler SynchronizedLifetimeManager, ou d'un descendant comme ContainerControlledLifetimeManager, mais il y avait deux scénarios, en particulier, qui ont été me causer des problèmes.
La première était de ma faute - j'ai été en utilisant le constructeur de l'injection de fournir une référence au conteneur, et dans ce constructeur, j'ai été également l'ajout de la nouvelle instance de la classe pour le conteneur pour une utilisation future. Cette rétro approche ayant pour effet de modifier la durée de vie du gestionnaire de Transitoire à ContainerControlled de sorte que l'objet de l'Unité appelée GetValue n'était pas le même objet, qu'il appelle SetValue. La leçon à retenir est de ne rien faire pendant la phase de construction qui pourrait changer la vie de l'objet gestionnaire.
Le deuxième scénario a été que chaque fois RegisterInstance est appelé, UnityDefaultBehaviorExtension appels SetValue sans appel GetValue premier. Heureusement, l'Unité est suffisamment extensible que, avec assez sanglante d'esprit, vous pouvez contourner le problème.
Démarrer avec un nouveau comportement de l'extension comme ceci:
/// <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();
}
}
}
Ensuite, vous avez besoin d'un moyen de remplacer le comportement par défaut. L'unité ne dispose pas d'une méthode pour supprimer une extension spécifique, de sorte que vous avez à tout supprimer et mettre les autres extensions de retour à nouveau:
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;
}
Notez que UnityClearBuildPlanStrategies
? RemoveAllExtensions efface toutes les conteneur des listes internes de politiques et de stratégies, sauf une, j'ai donc dû utiliser un autre poste pour éviter l'insertion de doublons quand j'ai restauré les extensions par défaut:
/// <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();
}
}
Maintenant, vous pouvez utiliser en toute sécurité RegisterInstance sans crainte d'être au bord de la folie. Juste pour être sûr, voici quelques tests:
[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 { }