6

Cette question concerne le conteneur Unity mais je suppose qu'il s'applique à tout conteneur de dépendances.Injection de cohérence: injection d'objets partiellement initialisés

J'ai deux classes de dépendances circulaires:

class FirstClass 
{ 
    [Dependency] 
    public SecondClass Second { get; set; } 
} 

class SecondClass 
{ 
    public readonly FirstClass First; 

    public SecondClass(FirstClass first) 
    { 
     First = first; 
    } 
} 

Techniquement, il est possible d'instancier et injecter correctement les dépendances pour les deux si les traiter comme des singletons:

var firstObj = new FirstClass(); 
var secondObj = new SecondClass(firstObj); 
firstObj.Second = secondObj; 

Lorsque je tente de faire la même chose avec Unity, je reçois StackOverflowException:

var container = new UnityContainer(); 
container.RegisterType<FirstClass>(new ContainerControlledLifetimeManager()); 
container.RegisterType<SecondClass>(new ContainerControlledLifetimeManager()); 

var first = container.Resolve<FirstClass>(); // StackOverflowException here! 
var second = container.Resolve<SecondClass>(); // StackOverflowException here too! 

Je comprends que Unity essaie de me protéger d'utiliser des objets partiellement initialisés mais je veux avoir cette protection comme une option, pas une obligation. Question: le comportement actuel est-il disabable?

Répondre

5

Je pense que vous ne pouvez pas utiliser de dépendances circulaires avec unité du tout.

Voir: http://msdn.microsoft.com/en-us/library/cc440934.aspx

+0

Je ne aime pas que l'unité ne peut instancier des classes, mais je peux le faire manuellement. A partir du lien MDSN que vous avez donné, aucun des cas suivants n'est vrai dans mon cas: -Objets générés par l'injection de constructeurs qui se référencent mutuellement dans leurs paramètres de constructeur -Objets générés par l'injection du constructeur lorsqu'une instance d'une classe est transmise en tant que paramètre à son propre constructeur -Objets générés par une injection d'appel de méthode se référant mutuellement -Objets générés par une injection de propriété (setter) se référant mutuellement –

+0

Le problème est que pendant quelques instants, "FirstClass" réside sans l'objet de dépendance, ce qui signifie que ce n'est pas vraiment valable. Tout cela dépend essentiellement de ce que font vos objets lorsqu'ils sont construits, si votre deuxième classe essayait d'appeler dans le premier, où le premier s'attendait alors à ce que l'objet dépendant soit disponible, alors cela échouerait. Les chaînes circulaires comme celle-ci sont problématiques, pour plusieurs raisons, vous devriez donc essayer d'éviter, si possible. –

+0

Je préférerais obtenir NRE plutôt que StackOverflowException, en particulier en tenant compte du fait que je peux instancier mes objets manuellement. –

2

Une façon ronde serait d'utiliser le chargement paresseux pour les dépendances sur l'une des classes:

[TestFixture] 
public class CircularUnityTest 
{ 
    IUnityContainer container; 

    [SetUp] 
    public void SetUp() 
    { 
     container = new UnityContainer(); 
     container.RegisterType(typeof(ILazy<>), typeof(Lazy<>)); 
     container.RegisterType<FirstClass>(new ContainerControlledLifetimeManager()); 
     container.RegisterType<SecondClass>(new ContainerControlledLifetimeManager()); 
    } 

    [Test] 
    public void CanResolveFirstClass() 
    { 
     var first = container.Resolve<FirstClass>(); 
     Assert.IsNotNull(first); 
    } 

    [Test] 
    public void CanResolveSecondClass() 
    { 
     var second = container.Resolve<SecondClass>(); 
     Assert.IsNotNull(second); 
    } 

    [Test] 
    public void CanGetFirstFromSecond() 
    { 
     var second = container.Resolve<SecondClass>(); 
     Assert.IsNotNull(second.First); 
    } 
} 

class FirstClass 
{ 
    [Dependency] 
    public SecondClass Second { get; set; } 
} 

class SecondClass 
{ 
    private readonly ILazy<FirstClass> lazyFirst; 

    public FirstClass First { get { return lazyFirst.Resolve(); } } 

    public SecondClass(ILazy<FirstClass> lazyFirst) 
    { 
     this.lazyFirst = lazyFirst; 
    } 
} 

public interface ILazy<T> 
{ 
    T Resolve(); 
} 

public class Lazy<T> : ILazy<T> 
{ 
    IUnityContainer container; 

    public Lazy(IUnityContainer container) 
    { 
     this.container = container; 
    } 

    public T Resolve() 
    { 
     return container.Resolve<T>(); 
    } 
} 
+2

C'est une idée intéressante! Ce que je n'aime pas c'est que mes classes devront connaître Unity et utiliser Lazy au lieu de références simples et claires. –

+0

Désolé d'intervenir sur ce deux ans plus tard, mais ... C'est exactement ce que je cherche, mais ça ne marche pas pour moi. Je reçois une exception de résolution de dépendance, disant qu'il ne peut pas résoudre 'ILazy '. Le code ci-dessus fonctionne-t-il toujours ou y a-t-il eu des changements à Unity qui nécessitent une approche différente? – Samo

+0

@Samo oui travaille toujours pour moi contre Unity 2.Quelle version de Unity utilisez-vous? Avez-vous copié mon code exactement? Vous devez enregistrer le générique ouvert de ILazy pour que cela fonctionne. (nb je l'ai écrit avant. NET 4 est venu avec son propre type Lazy qui est légèrement différent) –

1

vous pouvez utiliser RegisterInstance au lieu de RegisterType pour atteindre votre objectif. Il se comportera comme singleton - utilisera la même instance à chaque fois que Resolve est invoqué. Jetez un oeil à cet exemple:

class FirstClass 
{ 
    [Dependency] 
    public SecondClass Second { get; set; } 
} 

class SecondClass 
{ 
    public readonly FirstClass First; 

    public SecondClass(FirstClass first) 
    { 
     First = first; 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     IUnityContainer container = new UnityContainer(); 
     var firstObj = new FirstClass(); 

     var secondObj = new SecondClass(firstObj); 
     firstObj.Second = secondObj; 

     // Register instance instead of type!!! 
     container.RegisterInstance<FirstClass>(firstObj); 
     container.RegisterType<SecondClass>(); 

     var first = container.Resolve<FirstClass>(); 
     var second = container.Resolve<SecondClass>(); 
    } 
} 

Cheers,

Pavel

+1

Vous créez manuellement les deux objets Ma question est ce que j'ai dit que je ne veux pas faire Mon objectif principal est que Unity le fasse pour moi. deviendra beaucoup plus laide dans une application du monde réel quand vous aurez des douzaines de composants, pas seulement deux. Aussi dans votre échantillon * first.Second * et * second.First * ne sera pas la même chose! –