Wracking my brain around this to no avail, wonder if anyone can be of help?
Getting a really frustrating casting issue that im sure will have a quick answer, but is probably just happening due to my limited understanding of generic type inference or something.
Thanks in advance!
Scenario is a number of "Step" ViewModels for a Wizard site. I'm creating Builder classes for each, and using a factory to grab the specific builder for the step that gets posted back to me, which is a Collection of IStepViewModel's.
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
{
}
The failing test!
[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);
}
The problem!
Unable to cast object of type 'Company.Namespace.SpecificViewModelBuilder ' to type 'Company.Namespace.Builders.IStepModelBuilder`1[Company.Namespace.IStepViewModel]'.
Factory Impl as follows using 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;
}
}
Registration of this:
container.AddFacility<TypedFactoryFacility>();
....
container
.Register(
Classes
.FromAssemblyContaining<StepViewModelSelector>()
.BasedOn<StepViewModelSelector>());
container
.Register(
Component
.For<IStepViewModelBuilderFactory>()
.AsFactory(c => c.SelectedWith<StepViewModelSelector>()));
Stacktrace:
System.InvalidCastException was unhandled by user code
HResult=-2147467262 Message=Unable to cast object of type 'Company.Namespace.SpecificViewModelBuilder' to type 'Company.Namespace.IStepModelBuilder`1[Company.Namespace.IStepViewModel]'. Source=DynamicProxyGenAssembly2 StackTrace: at Castle.Proxies.IStepViewModelBuilderFactoryProxy.Create[T](T stepViewModel) at Tests.Infrastructure.ViewModelBuilderFactoryTests.TestResolution() in c:\Project\Infrastructure\ViewModelBuilderFactoryTests.cs:line 61
InnerException:
EDIT: IModelBuilder<T>
interface
public interface IModelBuilder<TViewModel>
{
TViewModel Build();
TViewModel Rebuild(TViewModel model);
}
There is one interface which you are not showing here, which is the IModelBuilder<T>
interface, but it's the key interface to solving your problem.
I'm assuming it's currently defined like this
public interface IModelBuilder<T> { }
If you use generic covariance, which is available since .NET 4, you'll be able to solve your problem by defining your interface like this:
public interface IModelBuilder<out T> { }
The out
modifier makes your interface covariant, which will allow you to cast from IStepModelBuilder<SpecificStepViewModel>
to IStepModelBuilder<IStepViewModel>
. You should note that this also puts a constraint on your interface which won't allow it to define any methods with T
as a parameter, but only as a return value.
You can read more about Covariance and Contravariance here.
EDIT
As you mentioned in your comment, your interface probably looks something like this:
public interface IModelBuilder<T>
{
T Create(T myViewModel);
}
If instead of passing T
as a parameter to Create
, it's OK for you to pass IStepViewModel
or anything other than T
instead, then this should solve your problem:
public interface IModelBuilder<out T>
{
T Create(IStepViewModel myViewModel);
}
If not, then your attempted cast really shouldn't be allowed.
I think the following two definitions are not compatibile
public interface IStepViewModelBuilderFactory
{
IStepModelBuilder<T> Create<T>(T stepViewModel) where T : IStepViewModel;
//... rest of the class definition
}
public class SpecificViewModelBuilder : IStepModelBuilder<SpecificStepViewModel>
{
}
When the Create runs, it casts the produced type (that is SpecificViewModelBuilder
) to his return value, that is IStepModelBuilder<T>
.
This cannot be done, you can test it by trying to do this manually:
class MyTest<T> where T : IStepViewModel
{
void Test()
{
IStepModelBuilder<T> cannotImplicitlyCast = new SpecificStepViewModelBuilder();
}
}
Edit: some (probably not so good) ideas
This can be done:
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>(); } }
so you could specialize the factories, one for each SpecificStepViewModel
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With