2016-04-05 19 views
6

Immagino che non ci sia modo di fare qualcosa di simile a quanto segue con Autofac, in modo da iniettare una serie enumerabile di tipi generici aperti? I vari tipi di handle hanno dipendenze, altrimenti li svilupperei solo dinamicamente.autofac Risolvi tutti i tipi di tipo generico aperto?

class EventOne : IEvent {...} 
    class EventTwo : IEvent {...} 
    class EventThree : IEvent {...} 
    interface IHandleEvent<T> where T : IEvent {...} 
    class HandleEventOne : IHandleEvent<EventOne> {...} 
    class HandleEventTwo : IHandleEvent<EventTwo> {...} 
    class HandleEventThree : IHandleEvent<EventThree> {...} 

    builder.RegisterAssemblyTypes(myAssembies).AsClosedTypesOf(typeof(IHandleEvent<>)); 
    builder.RegisterType<AService>().As<IAService>(); 


    class AService : IAService 
    { 
     public AService(IEnumerable<IHandleEvent<IEvent>> handles) 
     {...} 
    } 
+0

'IEnumerable >' avrebbe bisogno di essere risolto esattamente 1 tipo concreto . Se si dispone di un tipo che può creare dinamicamente se stesso (vedere Schema generatore), è possibile definirlo e registrarlo con AutoFac come registrazione per "IEnumerable >". È inoltre possibile iniettare il contenitore DI in questo tipo se è necessario recuperare informazioni aggiuntive per crearlo. In breve, penso che tu abbia bisogno di uno schema costruttore o fabbrica per risolvere la tua istanza 'IEnumerable >'. – Igor

+1

Grazie. Ho dato un'occhiata alle fabbriche delegate autofac doc. Sembra che in questo caso il contenitore venga iniettato in fabbrica per risolvere i vari tipi di IHandleEvent <>. – Suedeuno

+0

Provare a cambiare il tuo 'IHandleEvent ' a 'IHandleEvent '. Autofac ha qualche supporto per la varianza e potrebbe raccogliere automaticamente le registrazioni quando lo fai. Ma dimentico sempre che Autofac supporta la covarianza o la contravarianza, quindi dovrai provarlo. – Steven

risposta

7

Come spiegato nei commenti, ciò che si vuole è impossibile da raggiungere in C# e con buona ragione. Se si fosse in grado di trasmettere un valore IHandleEvent<EventOne> a un IHandleEvent<IEvent>, sarebbe consentito inoltrare anche uno EventTwo, il che non riuscirebbe in fase di esecuzione.

Quindi, ciò di cui hai bisogno è un'astrazione del mediatore che consenta di chiamare tutti i gestori di eventi compatibili. Tale mediatore è spesso chiamato IEventPublisher e potrebbe essere simile a questo:

public interface IEventPublisher { 
    void Publish(IEvent e); 
} 

è ora possibile creare una specifica implementazione contenitore. Per esempio, per Autofac questo apparirebbe come segue:

public class AutofacEventPublisher : IEventPublisher { 
    private readonly IComponentContext container; 

    public AutofacBusinessRuleValidator(IComponentContext container) { 
     this.container = container; 
    } 

    public void Publish(IEvent e) { 
     foreach (dynamic handler in this.GetHandlers(e.GetType())) { 
      handler.Handle((dynamic)e); 
     } 
    } 

    private IEnumerable GetHandlers(Type eventType) => 
     (IEnumerable)this.container.Resolve(
      typeof(IEnumerable<>).MakeGenericType(
       typeof(IHandleEvent<>).MakeGenericType(eventType))); 
} 

I consumatori possono ora dipendere da questa nuova astrazione:

class AService : IAService 
{ 
    public AService(IEventPublisher publisher) {...} 
} 
+0

L'unico modo per farlo con autofac è con lo schema di localizzazione per risolvere i tipi. Potrei semplicemente chiudere il tipo e utilizzare metodi generici in modo che autofac possa gestire naturalmente l'iniezione. – Suedeuno

+0

@ Suedeuno: non sei corretto. Quello che sto mostrando è * non * a * Service Locator *, a patto che 'AutofacEventPublisher' sia parte della tua [Composition Root] (http://blog.ploeh.dk/2011/07/28/CompositionRoot/), perché Service Locator riguarda [il ruolo, non i meccanismi] (http://blog.ploeh.dk/2011/08/25/ServiceLocatorrolesvs.mechanics/). Ma hai ragione nel sostenere che l'astrazione mediatrice è l'unica via; non solo in Autofac, ma con qualsiasi contenitore o anche senza contenitore. Questo a causa delle astrazioni che usi. – Steven

+0

Quindi non importa che AutofacEventPublisher risieda fuori dal modulo di avvio (assembly ui mvc, ad esempio, come indicato nel post del blog a cui si fa riferimento)? – Suedeuno

0

Non sarà in grado di lanciare IHandleEvent<EventThree>-IHandleEvent<IEvent> perché IHandleEvent<T> non è una covariante è possibile aggiungerlo aggiungendo il modificatore out.

public interface IHandleEvent<out TEvent> 
    where TEvent : IEvent 
{ } 

Purtroppo Autofac non supporta il tipo covariante ma solo di tipo controvariante. A proposito è possibile creare un'implementazione personalizzata IRegistrationSource per avere il comportamento richiesto. Qualcosa di simile a questo:

public class CovariantHandleEventRegistrationSource : IRegistrationSource 
{ 
    public bool IsAdapterForIndividualComponents 
    { 
    get 
    { 
     return false; 
    } 
    } 

    public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, 
           Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor) 
    { 
    IServiceWithType typedService = service as IServiceWithType; 
    if (typedService == null) 
    { 
     yield break; 
    } 

    if (typedService.ServiceType.IsGenericType && typedService.ServiceType.GetGenericTypeDefinition() == typeof(IHandleEvent<>)) 
    { 
     IEnumerable<IComponentRegistration> eventRegistrations = registrationAccessor(new TypedService(typeof(IEvent))); 

     foreach (IComponentRegistration eventRegistration in eventRegistrations) 
     { 
     Type handleEventType = typeof(IHandleEvent<>).MakeGenericType(eventRegistration.Activator.LimitType); 
     IComponentRegistration handleEventRegistration = RegistrationBuilder.ForDelegate((c, p) => c.Resolve(handleEventType, p)) 
              .As(service) 
              .CreateRegistration(); 

     yield return handleEventRegistration; 
     } 
    } 
    } 
} 

Con questo IRegistrationSource si può avere questo:

ContainerBuilder builder = new ContainerBuilder(); 
builder.RegisterType<EventOne>().As<IEvent>(); 
builder.RegisterType<EventTwo>().As<IEvent>(); 
builder.RegisterType<EventThree>().As<IEvent>(); 
builder.RegisterAssemblyTypes(typeof(Program).Assembly) 
     .AsClosedTypesOf(typeof(IHandleEvent<>)); 

builder.RegisterSource(new CovariantHandleEventRegistrationSource()); 

IContainer container = builder.Build(); 

var x = container.Resolve<IEnumerable<IHandleEvent<IEvent>>>(); 
+1

La controvarianza è in realtà ciò di cui OP ha bisogno; non covarianza. Un 'IHandleEvent ' accetta sempre un 'T' come argomento di input (è improbabile che un gestore restituisca un evento). In altre parole: "IHandleEvent ". – Steven

+0

@Steven hai ragione. In base al nome dell'interfaccia, sembra che 'IHandleEvent ' abbia un metodo 'Handle (T e)', quindi è necessaria la controvarianza.In questo caso, anche senza 'Autofac' non esiste un modo C# per costruire un' IHandleEvent ',' AService' dovrebbe essere generico e avere una dipendenza da 'IHandleEvent '. –

+1

Sì, hai ragione. Per poter trasmettere una raccolta di 'IHandleEvent ' a 'IHandleEvent ' è necessario covarianza (ad esempio, applicare la parola chiave 'out'), ma i gestori di eventi ovviamente devono essere controvarianti (ad esempio, utilizzare la parola chiave' in'). Quindi ciò che l'OP vuole è impossibile. – Steven

Problemi correlati