2013-07-19 27 views
5

Distruggere il mio cervello attorno a questo senza alcun risultato, mi chiedo se qualcuno può essere di aiuto?ViewModelBuilder generics casting issue

Ottenere un problema di casting davvero frustrante che sicuramente avrà una risposta rapida, ma probabilmente sta appena accadendo a causa della mia comprensione limitata dell'inferenza di tipo generico o qualcosa del genere.

Grazie in anticipo!

Scenario è un numero di ViewModels "Step" per un sito di procedura guidata. Sto creando classi Builder per ciascuno, e usando una factory per prendere il builder specifico per il passo che mi viene postato, che è una collezione di IStepViewModel.

public interface IStepViewModelBuilderFactory 
{ 
    IStepModelBuilder<T> Create<T>(T stepViewModel) where T : IStepViewModel; 
    void Release<T>(IStepModelBuilder<T> stepViewModelBuilder) where T : IStepViewModel; 
} 

public interface IStepViewModel 
{ 
} 

public interface IStepModelBuilder<TStepViewModel> : IModelBuilder<TStepViewModel> where TStepViewModel : IStepViewModel 
{ 
} 

public class SpecificViewModelBuilder : IStepModelBuilder<SpecificStepViewModel> 
{ 
} 

public class SpecificStepViewModel: StepViewModel 
{ 
} 

public abstract class StepViewModel : IStepViewModel 
{ 
} 

Il test fallito!

[Test] 
public void TestResolution() 
{ 
    var factory = this.container.Resolve<IStepViewModelBuilderFactory>(); 

    IStepViewModel viewModel = new SpecificStepViewModel(); 

    var builder = factory.Create(viewModel); // Here 

    Assert.That(builder, Is.Not.Null); 
} 

Il problema!

Impossibile eseguire il cast oggetto di tipo 'Company.Namespace.SpecificViewModelBuilder' di digitare 'Company.Namespace.Builders.IStepModelBuilder`1 [Company.Namespace.IStepViewModel]'.

fabbrica Impl come segue utilizzando Castle.Windsor

public class StepViewModelSelector : DefaultTypedFactoryComponentSelector 
{   
    protected override Type GetComponentType(System.Reflection.MethodInfo method, object[] arguments) 
    { 
     var arg = arguments[0].GetType(); 
     var specType = typeof(IModelBuilder<>).MakeGenericType(arg); 
     return specType; 
    } 
} 

registrazione di questo:

container.AddFacility<TypedFactoryFacility>(); 

    .... 

    container 
     .Register(
      Classes 
       .FromAssemblyContaining<StepViewModelSelector>() 
       .BasedOn<StepViewModelSelector>()); 

    container 
     .Register(
      Component 
       .For<IStepViewModelBuilderFactory>() 
       .AsFactory(c => c.SelectedWith<StepViewModelSelector>())); 

Stacktrace:

System.InvalidCastException è stata gestita dal codice utente
HResult = -21 47467262 Messaggio = Impossibile eseguire il cast dell'oggetto di tipo 'Company.Namespace.SpecificViewModelBuilder' per digitare 'Company.Namespace.IStepModelBuilder`1 [Company.Namespace.IStepViewModel]'. Source = DynamicProxyGenAssembly2 StackTrace: a Castle.Proxies.IStepViewModelBuilderFactoryProxy.Create [T] (T stepViewModel) a Tests.Infrastructure.ViewModelBuilderFactoryTests.TestResolution() in c: \ Project \ Infrastructure \ ViewModelBuilderFactoryTests.cs: linea 61
InnerException:

EDIT: IModelBuilder<T> interfaccia

public interface IModelBuilder<TViewModel> 
{ 
    TViewModel Build(); 
    TViewModel Rebuild(TViewModel model); 
} 
+0

Sembra che l'errore si verifichi all'interno dell'implementazione del metodo Create di quella fabbrica. potresti fornirlo per favore? – br1

+0

Sto solo usando il impl Typed factory di Castle.Windsor - si aggiunge il selettore del filtro usando. – M05Pr1mty

+0

ok per favore aggiungi il dump .ToString() dell'eccezione se puoi. Mi piacerebbe vedere la traccia dello stack – br1

risposta

4

C'è un'interfaccia che non sta mostrando qui, che è il IModelBuilder<T> interfaccia, ma è l'interfaccia chiave per risolvere il tuo problema.

sto supponendo che è attualmente definito come questo

public interface IModelBuilder<T> { } 

Se si utilizza covarianza generica, che è disponibile da allora.NET 4, sarete in grado di risolvere il problema definendo l'interfaccia come questa:

public interface IModelBuilder<out T> { } 

Il modificatore out rende il vostro covariante interfaccia, che vi permetterà di lanciare da IStepModelBuilder<SpecificStepViewModel> a IStepModelBuilder<IStepViewModel>. Si noti che ciò pone anche un vincolo sull'interfaccia che non gli consente di definire alcun metodo con T come parametro, ma solo come valore di ritorno.

Ulteriori informazioni su Covariance and Contravariance here.

EDIT

Come lei ha ricordato nel tuo commento, l'interfaccia appare probabilmente qualcosa di simile:

public interface IModelBuilder<T> 
{ 
    T Create(T myViewModel); 
} 

Se invece di passare T come parametro per Create, va bene per voi di passare IStepViewModel o qualsiasi altra cosa diversa da T invece, questo dovrebbe risolvere il tuo problema:

public interface IModelBuilder<out T> 
{ 
    T Create(IStepViewModel myViewModel); 
} 

In caso contrario, il tuo tentativo di lancio non dovrebbe essere consentito.

+0

Ho visto anche questo, aggiungerò l'interfaccia, ma non penso di poter usare la covarianza con questo come im usando il tipo anche come parametro di input e quindi, per quanto ne so, ha bisogno di essere invariato? – M05Pr1mty

+0

Grazie per quello - davvero utile.Quindi, come è il caso, c'è un modo per ristrutturare tutto ciò per consentire ciò che sto cercando di ottenere? – M05Pr1mty

+0

@ M05Pr1mty Vedere la mia risposta aggiornata –

2

penso che le due seguenti definizioni non sono compatibile

public interface IStepViewModelBuilderFactory 
{ 
    IStepModelBuilder<T> Create<T>(T stepViewModel) where T : IStepViewModel; 
    //... rest of the class definition 
} 

public class SpecificViewModelBuilder : IStepModelBuilder<SpecificStepViewModel> 
{ 
} 

Quando il Crea corre, si getta il tipo di prodotto (cioè SpecificViewModelBuilder) al suo valore di ritorno, che è IStepModelBuilder<T>.

Questo non può essere fatto, è possibile verificare che cercando di farlo manualmente:

class MyTest<T> where T : IStepViewModel 
    { 
     void Test() 
     { 
      IStepModelBuilder<T> cannotImplicitlyCast = new SpecificStepViewModelBuilder(); 
     } 
    } 

Edit: alcuni (probabilmente non così buono) idee

questo può essere fatto:

public class ViewModelBuilder<T> : IStepModelBuilder<T> where T : IStepViewModel 
{ 
} 

class MyTest<T> where T : IStepViewModel 
{ 
    void Test() 
    { 
     IStepModelBuilder<T> ok= new ViewModelBuilder<T>(); 
     IStepModelBuilder<SpecificStepViewModel> alsoOk = new ViewModelBuilder<SpecificStepViewModel>(); 
    } 
} 

così si potrebbe SPECIALI ze le fabbriche, una per ogni SpecificStepViewModel

+0

Sono d'accordo - avete qualche suggerimento su come risolvere questo problema? O l'ho guardato troppo a lungo o è al di là di me! – M05Pr1mty

+0

Idea interessante - Potrei tornare a questo in un secondo momento. Forse una sorta di factoy astratto potrebbe essere avvolto attorno a questo per servire la fabbrica specifica giusta ... Grazie a prescindere! – M05Pr1mty