Pregunta Problema de conversión de genéricos de ViewModelBuilder [duplicado]


Esta pregunta ya tiene una respuesta aquí:

Dando vueltas en mi cerebro sin resultado, me pregunto si alguien puede ser de ayuda.

Obtener un problema de casting realmente frustrante que estoy seguro tendrá una respuesta rápida, pero probablemente solo esté sucediendo debido a mi comprensión limitada de la inferencia de tipo genérico o algo así.

¡Gracias por adelantado!

El escenario es un número de ViewModels "Step" para un sitio de asistente. Estoy creando clases de Builder para cada uno, y uso una fábrica para capturar el constructor específico para el paso que se publica, que es una Colección de 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
{
}

¡La prueba que falla!

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

¡El problema!

No se puede lanzar objeto de tipo   'Company.Namespace.SpecificViewModelBuilder' para escribir   'Company.Namespace.Builders.IStepModelBuilder`1 [Company.Namespace.IStepViewModel]'.

Factory Impl como sigue usando 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;
    }
}

Registro de este:

container.AddFacility<TypedFactoryFacility>();

     ....

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

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

Stacktrace:

System.InvalidCastException no fue manejado por el código de usuario
  HResult = -2147467262 Mensaje = No se puede convertir el objeto de tipo   'Company.Namespace.SpecificViewModelBuilder' para escribir   'Company.Namespace.IStepModelBuilder`1 [Company.Namespace.IStepViewModel]'.   Fuente = DynamicProxyGenAssembly2 StackTrace:          en Castle.Proxies.IStepViewModelBuilderFactoryProxy.Create [T] (T stepViewModel)          en Tests.Infrastructure.ViewModelBuilderFactoryTests.TestResolution () en   c: \ Proyecto \ Infraestructura \ ViewModelBuilderFactoryTests.cs: línea 61
  InnerException:

EDITAR: IModelBuilder<T> interfaz

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

5
2017-07-19 11:48


origen


Respuestas:


Hay una interfaz que no está mostrando aquí, que es la IModelBuilder<T> interfaz, pero es la interfaz clave para resolver su problema.

Supongo que actualmente está definido como este

public interface IModelBuilder<T> { }

Si usa la covarianza genérica, que está disponible desde .NET 4, podrá resolver su problema definiendo su interfaz de la siguiente manera:

public interface IModelBuilder<out T> { }

los out modificador hace que su interfaz sea covariante, lo que le permitirá lanzar desde IStepModelBuilder<SpecificStepViewModel> a IStepModelBuilder<IStepViewModel>. Debe tener en cuenta que esto también pone una restricción en su interfaz que no le permitirá definir ningún método con T como parámetro, pero solo como valor de retorno.

Puedes leer más sobre Covarianza y Contravarianza aquí.

EDITAR

Como mencionaste en tu comentario, tu interfaz probablemente se vea algo como esto:

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

Si en lugar de pasar T como un parámetro para Create, esta bien que pases IStepViewModel o cualquier otra cosa que no sea T en su lugar, entonces esto debería resolver su problema:

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

Si no, entonces tu intento de lanzamiento no debería ser permitido.


4
2017-07-19 12:49



Creo que las siguientes dos definiciones no son compatibles.

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

public class SpecificViewModelBuilder : IStepModelBuilder<SpecificStepViewModel>
{
}

Cuando se ejecuta la creación, se convierte el tipo producido (es decir, SpecificViewModelBuilder) a su valor de retorno, que es IStepModelBuilder<T>.

Esto no se puede hacer, puede probarlo intentando hacerlo manualmente:

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

Edición: algunas (probablemente no tan buenas) ideas

Esto puede hacerse:

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

por lo que se especializa en las fábricas, una para cada modelo de vista específico.


2
2017-07-19 12:57