6

Questa domanda riguarda il contenitore di unità, ma suppongo che sia applicabile a qualsiasi contenitore di dipendenze.Iniezione di priorità: iniezione di oggetti parzialmente inizializzati

ho due classi con dipendenze circolari:

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

class SecondClass 
{ 
    public readonly FirstClass First; 

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

Tecnicamente è possibile istanziare e iniettare dipendenze per entrambi correttamente se trattarli come singletons:

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

Quando provo a fare lo stesso con Unity, ottengo 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! 

Capisco che Unity cerchi di proteggermi dall'usare oggetti parzialmente inizializzati, ma voglio avere questa protezione come un'opzione, non un obbligo.

Domanda: il comportamento corrente non è adatto?

risposta

5

Penso che non sia possibile utilizzare le dipendenze circolari con l'unità.

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

+0

non mi piace che l'unità non può istanziare le classi ma posso farlo manualmente. Dal collegamento MDSN fornito, nessuna delle seguenti condizioni è vera per il mio caso: -Oggetti generati tramite un'iniezione del costruttore che si richiamano nei parametri del costruttore -Oggetti generati tramite l'iniezione del costruttore dove un'istanza di una classe viene passata come parametro al proprio costruttore -Oggetti generati tramite l'iniezione di chiamata al metodo che fanno riferimento l'un l'altro -Oggetti generati tramite l'iniezione di proprietà (setter) che fanno riferimento l'un l'altro –

+0

Il problema è che per alcuni momenti, "FirstClass" risiede senza l'oggetto di dipendenza, il che significa che non è veramente valido. Tutto ciò dipende fondamentalmente da ciò che i tuoi oggetti fanno quando sono costruiti, se la tua seconda classe cercherebbe di chiamare il primo, dove il primo si aspetta che l'oggetto dipendente sia disponibile, allora fallirebbe. Catene circolari come questa sono problematiche, per diverse ragioni, quindi dovresti cercare di evitare, se possibile. –

+0

Preferirei ottenere NRE piuttosto che StackOverflowException, soprattutto tenendo conto che posso istanziare manualmente i miei oggetti. –

2

Un modo intorno a questo sarebbe quella di utilizzare lazy loading per le dipendenze su una delle classi:

[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

Questa è un'idea interessante! Quello che non mi piace è che le mie lezioni dovranno conoscere Unity e io devo usare Lazy invece di riferimenti semplici e chiari. –

+0

Siamo spiacenti di intervenire su questo due anni dopo, ma ... Questo è esattamente quello che sto cercando, ma non funziona per me. Ottengo un'eccezione di risoluzione delle dipendenze, dicendo che non può risolvere "ILazy ". Il codice sopra funziona ancora o sono state apportate modifiche a Unity che richiedono un approccio diverso? – Samo

+0

@Samo si sta ancora lavorando per me contro Unity 2.Quale versione di Unity stai usando? Hai copiato il mio codice esattamente? È necessario registrare il generico aperto di ILazy per farlo funzionare. (nb l'ho scritto prima che .NET 4 venisse con un proprio tipo Lazy che è leggermente diverso) –

1

è possibile utilizzare registerInstance invece di RegisterType per raggiungere il tuo obiettivo. Si comporterà proprio come Singleton - utilizzerà la stessa istanza ogni volta che viene invocato Resolve. Date un'occhiata a questo esempio:

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

Stai creando manualmente entrambi gli oggetti La mia domanda è ciò che ho detto che non voglio fare Il mio obiettivo principale è far sì che Unity lo faccia per me. diventerà molto più brutto nell'applicazione del mondo reale quando avrai dozzine di componenti, non solo due, anche nel tuo campione * first.Second * and * second.First * non sarà lo stesso! –