5

Iniziamo con una storia autonoma, solo per capire perché: Voglio trattare qualsiasi azione che modifica i dati sulla stessa interfaccia: ICommand Ci sono cose che esistono là fuori chiamate ICommandHandlers che gestiscono ogni comando che voglio. Quindi, se voglio un CreatePersonCommand, mi serve un CreatePersonCommandHandler.RegisterOpenGeneric con SimpleInjector risolve il tipo errato

Quindi, ecco il corpo di un'applicazione console che illustra questo: (Richiede Simple Injector)

// The e.g. CreatePersonCommand, with TResult being Person, as an example. 
public interface ICommand<TResult> 
{ 
} 

//This handles the command, so CreatePersonCommandHandler 
public interface ICommandHandler<in TCommand, out TResult> 
    where TCommand : ICommand<TResult> 
{ 
    TResult Handle(TCommand command); 
} 

// Imagine a generic CRUD set of operations here where we pass 
// in an instance of what we need made 
public class CreateBaseCommand<TModel> : ICommand<TModel> 
{ 
    public TModel ItemToCreate { get; set; } 
} 

public class DeleteBaseCommand<TModel> : ICommand<TModel> 
{ 
    public TModel ItemToDelete { get; set; } 
} 

public class CreateCommandBaseHandler<TModel> 
    : ICommandHandler<CreateBaseCommand<TModel>, TModel> 
{ 
    public TModel Handle(CreateBaseCommand<TModel> command) 
    { 
     // create the thing 
     return default (TModel); 
    } 
} 

public class DeleteCommandBaseHandler<TModel> 
    : ICommandHandler<DeleteBaseCommand<TModel>, TModel> 
{ 
    public TModel Handle(DeleteBaseCommand<TModel> command) 
    { 
     // delete the thing 
     return default(TModel); 
    } 
} 

public class Program 
{ 
    private static Container container; 

    static void Main(string[] args) 
    { 
     container = new Container(); 

     // Order does not seem to matter, I've tried both ways. 
     container.RegisterOpenGeneric(typeof(ICommandHandler<,>), 
      typeof(DeleteCommandBaseHandler<>)); 
     container.RegisterOpenGeneric(typeof(ICommandHandler<,>), 
      typeof(CreateCommandBaseHandler<>)); 

     container.Verify(); 

     // So I want to make the usual hello world 
     var commandToProcess = new CreateBaseCommand<string> { ItemToCreate = "hello world"}; 

     // Send it away! 
     Send(commandToProcess); 
    } 

    private static void Send<TResult>(ICommand<TResult> commandToProcess) 
    { 
     //{CreateBaseCommand`1[[System.String,..."} 
     var command = commandToProcess.GetType(); 
     //{Name = "String" FullName = "System.String"} 
     var resultType = typeof (TResult); 

     //"ICommandHandler`2[[CreateBaseCommand`1[[System.String,..."} 
     // so it's the right type here 
     var type = typeof(ICommandHandler<,>).MakeGenericType(command, resultType); 

     // This is where we break! 
     var instance = container.GetInstance(type); 
     // The supplied type DeleteCommandBaseHandler<String> does not implement 
     // ICommandHandler<CreateBaseCommand<String>, String>. 
     // Parameter name: implementationType 
    } 
} 

Quindi per qualsiasi motivo SimpleInjector cerca sempre di risolvere il DeleteCommandHandler<> per il CreateBaseCommand<> che ho. Ancora, l'ordine non ha importanza. Ho altri gestori di comandi di tipo chiuso (ei loro rispettivi comandi) che ereditano solo ICommandHandler<,> che funzionano correttamente.

Ho passato un bel po 'di tempo a esaminare ogni possibile tipo di registrazione che potevo da this.

+0

Il bug che si è verificato è davvero fastidioso. Ho appena incontrato lo stesso problema quando ho scritto un decoratore in una delle mie applicazioni. Sto considerando di implementare una patch release (2.3.6) per correggere questo bug, invece di aspettare il prossimo minor (2.4), poiché in alcuni scenari è davvero difficile aggirare questo bug. – Steven

+0

Questa è una buona notizia, non mi aspettavo altri casi da solo. –

risposta

4

UPDATE:

Questo è sicuramente un bug nella release corrente. Questo in qualche modo è scivolato attraverso le cricche di test dell'unità. Il codice manca un controllo che verifica se un'implementazione generica chiusa costruita implementa effettivamente il tipo di servizio generico chiuso richiesto. Se tutti i vincoli di tipo generico sono validi, il framework considera la risoluzione corretta, il che non è corretto nel tuo caso.

La correzione è piuttosto semplice e la prossima versione v2.4 risolverà sicuramente questo problema, ma nel frattempo dovrai risolvere il problema.

UPDATE 2:

La realtà è piuttosto brutto e può essere molto difficile da aggirare, in alcuni casi. Oltre allo RegisterOpenGeneric, anche le registrazioni dei decoratori sono interessate. Questo mi ha fatto concludere che questo doveva essere risolto velocemente e non posso aspettare fino alla prossima versione secondaria. Ho quindi spinto Version 2.3.6 in NuGet e CodePlex. v2.3.6 risolve questo problema.

SOLUZIONE:

La soluzione è quella di evitare che forniscono argomenti di tipo generico che sono nidificati in altri tipi (come il tuo DeleteBaseCommand<TModel>). Invece è possibile ricorrere all'utilizzo di vincoli di tipo generico, come si può vedere nel seguente esempio:

public class CreateCommandBaseHandler<TCommand, TModel> 
    : ICommandHandler<TCommand, TModel> // no nested generic arguments here 
    where TCommand : CreateBaseCommand<TModel> // but type constraint here. 
{ 
    public TModel Handle(TCommand command) 
    { 
     // create the thing 
     return default(TModel); 
    } 
} 

public class DeleteCommandBaseHandler<TCommand, TModel> 
    : ICommandHandler<TCommand, TModel> 
    where TCommand : DeleteBaseCommand<TModel> 
{ 
    public TModel Handle(TCommand command) 
    { 
     // delete the thing 
     return default(TModel); 
    } 
} 
Problemi correlati