2013-07-19 16 views
5

Obijanie mojego mózgu wokół tego bez skutku, zastanawiam się, czy ktoś może być pomocny?ViewModelBuilder generics casting issue

Uzyskanie naprawdę frustrującego problemu z castingiem, który na pewno będzie miał szybką odpowiedź, ale prawdopodobnie dzieje się tak po prostu ze względu na moje ograniczone zrozumienie ogólnego rodzaju wnioskowania lub coś takiego.

Z góry dziękuję!

Scenariusz to liczba modeli ViewModels "Step" dla witryny kreatora. Tworzę klasy Buildera dla każdego z nich i używam fabryki, aby pobrać konkretny program budujący dla kroku, który został wysłany do mnie, co jest kolekcją 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 
{ 
} 

Awaria testu!

[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); 
} 

Problem!

nie można rzutować obiektu typu 'Company.Namespace.SpecificViewModelBuilder' wpisać 'Company.Namespace.Builders.IStepModelBuilder`1 [Company.Namespace.IStepViewModel]'.

Fabryka IMPL następująco użyciu 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; 
    } 
} 

rejestracji to:

container.AddFacility<TypedFactoryFacility>(); 

    .... 

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

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

StackTrace:

System.InvalidCastException był nieobsługiwany przez kod użytkownika
HResult = -21 47467262 Message = Nie można rzutować obiektu typu "Company.Namespace.SpecificViewModelBuilder", aby wpisać "Company.Namespace.IStepModelBuilder`1 [Company.Namespace.IStepViewModel]". Źródło = DynamicProxyGenAssembly2 StackTrace: w Castle.Proxies.IStepViewModelBuilderFactoryProxy.Create [T] (T stepViewModel) w Tests.Infrastructure.ViewModelBuilderFactoryTests.TestResolution() w c: \ \ Projekt infrastruktury \ ViewModelBuilderFactoryTests.cs: linia 61
InnerException:

EDIT: IModelBuilder<T> interfejs

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

Wygląda na to, że błąd występuje wewnątrz implementacji metody Create tej fabryki. czy mógłbyś to dostarczyć? – br1

+0

Po prostu używam fabryki Typed z Castle.Windsor - ill add w im selektorze filtra. – M05Pr1mty

+0

ok, proszę dodać zrzut .ToString() wyjątku, jeśli możesz. Chciałbym zobaczyć ślad stosu – br1

Odpowiedz

4

jest jeden interfejs, który nie wykazują tu, który jest IModelBuilder<T> interfejs, ale jest to kluczowy interfejs do rozwiązania problemu.

Ja zakładając, że aktualnie zdefiniowane jak ten

public interface IModelBuilder<T> { } 

Jeśli używasz rodzajowe kowariancji, który jest dostępny od.NET 4, będziesz w stanie rozwiązać problemu poprzez zdefiniowanie interfejsu tak:

public interface IModelBuilder<out T> { } 

out modyfikator sprawia kowariantna interfejsu, który pozwoli Ci rzucić z IStepModelBuilder<SpecificStepViewModel> do IStepModelBuilder<IStepViewModel>. Należy zauważyć, że nakłada to ograniczenie na interfejs użytkownika, co nie pozwala zdefiniować żadnych metod za pomocą parametru T, ale tylko jako wartość zwracaną.

Możesz przeczytać więcej o Covariance and Contravariance here.

EDIT

Jak wspomniano w komentarzu, twój interfejs prawdopodobnie wygląda mniej więcej tak:

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

Jeśli zamiast przechodzenia T jako parametr do Create, to OK, aby przejść IStepViewModel lub cokolwiek innego niż T zamiast tego, to powinno rozwiązać twój problem:

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

Jeśli nie, to próba rzucenia naprawdę nie powinna być dozwolona.

+0

Widziałem to również, dodam interfejs, ale nie sądzę, że jestem w stanie użyć kowariancji z tym jako im używając tego typu jako parametru wejściowego, a więc o ile wiem, że musi być niezmienna? – M05Pr1mty

+0

Dzięki za to - naprawdę przydatne.A więc tak właśnie jest - czy istnieje sposób na restrukturyzację tego wszystkiego, aby umożliwić to, co próbuję osiągnąć? – M05Pr1mty

+0

@ M05Pr1mty Zobacz moją zaktualizowaną odpowiedź –

2

myślę dwa następujące definicje nie są kompatybilne

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

public class SpecificViewModelBuilder : IStepModelBuilder<SpecificStepViewModel> 
{ 
} 

Gdy Tworzenie tras, to rzuca wyprodukowany typ (czyli SpecificViewModelBuilder) do jego wartości zwracanej, czyli IStepModelBuilder<T>.

To nie może być zrobione, można go przetestować, próbując to zrobić ręcznie:

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

Edycja: niektóre (zapewne nie tak dobre) pomysły

można to zrobić:

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>(); 
    } 
} 

więc można sPECIALI ze wszystkich fabryk, po jednym dla każdego SpecificStepViewModel

+0

Zgadzam się - czy masz jakieś sugestie, jak rozwiązać ten problem? Albo patrzyłem na to zbyt długo, albo poza mną! – M05Pr1mty

+0

Interesujący pomysł - mogę do tego wrócić w późniejszym terminie. Być może jakiś abstrakcyjny factoy mógłby zostać zawinięty wokół tego, aby służyć odpowiedniej właściwej fabryce ... Dzięki, niezależnie! – M05Pr1mty