2016-01-20 12 views
26

che sto usando di Unity Registrati per convenzione meccanismo nel seguente scenario:Perché un tipo è registrato due volte quando è specificato il gestore della durata?

public interface IInterface { } 

public class Implementation : IInterface { } 

Dato Implementation classe e la sua interfaccia sto correndo RegisterTypes nel seguente modo:

unityContainer.RegisterTypes(
    new[] { typeof(Implementation) }, 
    WithMappings.FromAllInterfaces, 
    WithName.Default, 
    WithLifetime.ContainerControlled); 

Dopo questo chiamare, unitContainer contiene tre registrazioni:

  • IUnityContainer ->IUnityContainer (ok)
  • IInterface ->Implementation (ok)
  • Implementation ->Implementation (???)

Quando cambio la chiamata come segue:

unityContainer.RegisterTypes(
    new[] { typeof(Implementation) }, 
    WithMappings.FromAllInterfaces, 
    WithName.Default); 

Il contenitore contiene solo due registrazioni:

  • IUnityContainer ->IUnityContainer (ok)
  • IInterface ->Implementation (ok)

(questo è il comportamento desiderato).

Dopo aver sbirciato in Unity's source code, ho notato che c'è qualche incomprensione su come IUnityContainer.RegisterType dovrebbe funzionare.

Il metodo RegisterTypes funziona nel modo seguente (i commenti che indicano quali sono i valori negli scenari presentati in precedenza):

foreach (var type in types) 
{ 
    var fromTypes = getFromTypes(type); // { IInterface } 
    var name = getName(type);   // null 
    var lifetimeManager = getLifetimeManager(type); // null or ContainerControlled 
    var injectionMembers = getInjectionMembers(type).ToArray(); // null 

    RegisterTypeMappings(container, overwriteExistingMappings, type, name, fromTypes, mappings); 

    if (lifetimeManager != null || injectionMembers.Length > 0) 
    { 
     container.RegisterType(type, name, lifetimeManager, injectionMembers); // ! 
    } 
} 

Perché fromTypes non è vuoto, il RegisterTypeMappings aggiunge un tipo di mappatura: IInterface ->Implementation (corretta).

Poi, nel caso in cui lifetimeManager non è nullo, il codice tenta di cambiare il manager vita con la seguente chiamata:

container.RegisterType(type, name, lifetimeManager, injectionMembers); 

nome di questa funzione è completamente fuorviante, perché the documentation afferma chiaramente che:

Register Digitare un LifetimeManager per il tipo e il nome specificati con il contenitore. Nessun tipo di mappatura viene eseguita per questo tipo.

Sfortunatamente, non solo il nome è fuorviante, ma la documentazione è sbagliata. Quando ho eseguito il debug di questo codice, ho notato che quando non c'è alcuna mappatura da type (Implementation negli scenari presentati sopra), viene aggiunto (come type ->type) ed è per questo che ci ritroviamo con tre registrazioni nel primo scenario .

Ho scaricato fonti di Unity per risolvere il problema, ma ho trovato il seguente test unità:

[TestMethod] 
public void RegistersMappingAndImplementationTypeWithLifetimeAndMixedInjectionMembers() 
{ 
    var container = new UnityContainer(); 
    container.RegisterTypes(new[] { typeof(MockLogger) }, getName: t => "name", getFromTypes: t => t.GetTypeInfo().ImplementedInterfaces, getLifetimeManager: t => new ContainerControlledLifetimeManager()); 

    var registrations = container.Registrations.Where(r => r.MappedToType == typeof(MockLogger)).ToArray(); 

    Assert.AreEqual(2, registrations.Length); 

    // ... 

- che è quasi esattamente il mio caso, e conduce alla mia domanda:

Perché è previsto? È un errore concettuale, un test unitario creato per corrispondere al comportamento esistente ma non necessariamente corretto, o mi manca qualcosa di importante?

Sto utilizzando Unity v4.0.30319.

risposta

1

Uno dei motivi che mi viene in mente per il comportamento attuale è che riutilizza la funzionalità/implementazione Unity esistente e rende la registrazione per convenzione una funzione abbastanza facile da implementare.

L'implementazione Unity esistente a cui sto pensando è la separazione di tipo mapping e build plan in diverse politiche, quindi se si sta lavorando al codice sorgente di Unity sarebbe un modo comune di pensare alle cose.Inoltre, da quello che ho letto in passato, la proprietà Registrations sembra essere stata pensata come un cittadino di seconda classe e non è stata pensata per essere utilizzata per molto più del debug, quindi avere un'altra registrazione potrebbe non sembrare un grosso problema . Forse quei due punti messi insieme erano parte della decisione?

Oltre all'elemento extra nelle raccolte di registrazioni, la funzione funziona in questo caso.

Quindi, nel caso in cui lifetimeManager non è nullo, il codice tenta di cambiare il gestore durata con la seguente chiamata

Il metodo RegisterTypes realtà non impostare un LifetimeManager. Se non è specificato LifetimeManager, il tipo di obiettivo concreto non viene registrato esplicitamente e Unity si basa sul comportamento predefinito interno durante la creazione dell'oggetto (in questo caso un valore predefinito LifetimeManager di TransientLifetimeManager).

Tuttavia, se viene specificato qualcosa che potrebbe sovrascrivere i valori predefiniti (ad esempio LifetimeManager o InjectionMembers), il tipo di calcestruzzo verrà registrato esplicitamente.

Dovrebbe essere possibile ottenere la registrazione per convenzione per lavorare senza la registrazione extra. Tuttavia, ci sono alcune complessità da considerare. Se un tipo concreto implementa molte interfacce, potrebbero esserci più registrazioni di mappatura per un tipo concreto, ma ciascuna registrazione avrà bisogno della propria istanza del gestore di durata (non possono essere riutilizzate). Quindi è possibile creare nuove istanze del gestore di durata per ciascuna registrazione di mapping ma (credo) solo l'ultimo gestore di durata verrebbe utilizzato. Pertanto, per evitare la creazione di oggetti non necessari, è possibile assegnare solo il gestore della durata nell'ultimo tipo di mappatura? Questo è solo uno scenario che mi è venuto in mente: ci sono una varietà di scenari e combinazioni da considerare.

Quindi penso che l'implementazione corrente sia un modo semplice per implementare la funzione senza dover gestire specificamente i casi di bordo con l'unico effetto collaterale della registrazione extra. Immagino che probabilmente era parte del pensiero in quel momento (ma non ero lì quindi è solo una supposizione).

+0

La parte relativa all'impossibilità di disporre di più istanze di un gestore lifetime predefinito quando ci sono più interfacce sembra essere un argomento valido e l'unica ragione concreta finora. Grazie! – BartoszKP

7

avremmo bisogno di entrare in contatto con gli sviluppatori originali per essere sicuri, ma questo è quello che posso solo supporre il motivo per cui è stato codificato per essere in questo modo ...

unità ha una funzione che consente di cemento classi da risolvere anche se il tipo non è stato registrato. In questo scenario, Unity deve presupporre che si desidera il gestore della durata predefinito (transitorio) e nessun membro di iniezione per quel tipo concreto. Se non ti piacciono queste politiche di default, allora devi registrare tu stesso il tipo concreto e specificare la tua personalizzazione.

Quindi, seguendo questa linea di pensiero, quando si chiama RegisterTypes e si specifica un gestore di durata e/o un membro di iniezione, quindi Unity fa presupporre che si desidera tale comportamento quando si risolve dall'interfaccia e dal tipo concreto. Ma se non si specificano tali politiche, quindi Unity non ha bisogno della registrazione concreta perché ricadrà sul comportamento predefinito durante la risoluzione del tipo concreto.

L'unica altra spiegazione che posso fornire è per motivi di plugin. InjectionMember è estendibile, quindi Unity pensa che potresti passare un comportamento personalizzato per un plug-in. Pertanto, si presuppone che si desideri che il comportamento personalizzato per il plug-in sia applicato all'interfaccia e al calcestruzzo quando si registra per convenzione.


Capisco che si stia verificando questo problema durante il tentativo di testare l'unità con il bootstrap dell'applicazione. Suppongo che tu stia eseguendo dei test per garantire i tipi che registri e solo quei tipi sono registrati. Questo sta infrangendo i test perché alcuni test trovano questa registrazione extra concreta.

Da una prospettiva di test di unità, direi che si sta andando oltre lo scopo di ciò che si sta tentando di testare. Se inizi a utilizzare plug-in, ci saranno un bel po 'di altre registrazioni che iniziano ad apparire (come politiche e comportamenti di intercettazione). Ciò si aggiungerà al problema che stai vedendo ora a causa del modo in cui stai testando. Ti consiglio di cambiare i test per assicurarti che sia registrata solo una white-list di tipi e ignorare tutto il resto. Avere il calcestruzzo (o altre registrazioni aggiuntive) è benigno per la tua applicazione.

(ad esempio mettere le interfacce nella white-list e far valere ogni uno di quelli è stato registrato e ignorano il fatto che il calcestruzzo è stato registrato)

+0

Stavo scrivendo unit test per un bootstrapper della mia applicazione e questo è stato molto sorprendente. Rende illeggibili i miei test: due scenari simili producono risultati diversi. TBH, non capisco la tua spiegazione. Avere una sola registrazione consente entrambi: il gestore della durata di default e quello specificato. Hai identificato correttamente il nocciolo del problema: "Unity fa presupporre che tu voglia quel comportamento quando si risolve dall'interfaccia ** e dal tipo concreto **" - ma questa è la domanda: perché? Quale scenario giustifica questa distinzione? Non riesco a capire perché sarebbe utile in alcun modo. – BartoszKP

+0

Risposta aggiornata con un altro possibile motivo (plug-in) e aggiunto un suggerimento per modificare i test dell'unità. – TylerOhlsen

+0

concordo sul fatto che dovresti testare ciò che ti interessa. non testare su alcuna possibile implementazione. – Batavia

Problemi correlati