ViewModelBuilder 类型转换问题

5

我已经努力思考,但没有结果。不知道有没有人能够帮忙?

我遇到了一个非常令人沮丧的问题,这个问题应该很快就能解决,但可能是由于我的泛型类型推断的理解有限或其他原因引起的。

提前感谢!

场景是一个向导站点的多个“步骤”ViewModel。我为每个ViewModel创建了构建器类,并使用工厂来获取返回给我的步骤的特定构建器,这是一个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
{
}

测试失败了!
[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);
}

问题!

无法将类型为 'Company.Namespace.SpecificViewModelBuilder' 的对象强制转换为类型 'Company.Namespace.Builders.IStepModelBuilder`1[Company.Namespace.IStepViewModel]'。

采用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;
    }
}

注册此内容:

container.AddFacility<TypedFactoryFacility>();

     ....

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

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

堆栈跟踪:

用户代码无法处理System.InvalidCastException异常
HResult=-2147467262 Message=无法将类型为“Company.Namespace.SpecificViewModelBuilder”的对象转换为类型 “Company.Namespace.IStepModelBuilder`1 [Company.Namespace.IStepViewModel]”。 来源=DynamicProxyGenAssembly2 StackTrace: at Castle.Proxies.IStepViewModelBuilderFactoryProxy.Create[T](T stepViewModel) at Tests.Infrastructure.ViewModelBuilderFactoryTests.TestResolution() in c:\Project\Infrastructure\ViewModelBuilderFactoryTests.cs:line 61
内部异常:

编辑:IModelBuilder<T>接口

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

看起来错误发生在该工厂的Create方法实现内部。你能提供它吗? - br1
我只是在使用Castle.Windsor的Typed工厂实现 - 我会添加我正在使用的过滤器选择器。 - M05Pr1mty
如果可以,请添加异常的.ToString()转储。我想查看堆栈跟踪。 - br1
我从未使用过Castle Windsor,但是我注意到你的代码中有IStepModelBuilder<TStepViewModel>。这个TStepViewModel真的应该是这样吗? - Steve
@AdiLester 谢谢,我看到了那个问题 - 不确定它如何与这个问题相关,或者至少我无法从中得出解决方法,有什么帮助吗? - M05Pr1mty
@Steve 谢谢 - 我正在使用 TStepViewModel 将类型传递到底层接口。据我所知,Windows 需要一个有类型的接口才能从池中进行选择。我可能是错的。 - M05Pr1mty
2个回答

4
这里有一个接口没有被展示出来,它是 IModelBuilder<T> 接口,但它是解决你问题的关键接口。
我假设它目前被定义为:
public interface IModelBuilder<T> { }

如果您使用自变量协变(generic covariance),这是自.NET 4以来可用的,您可以通过以下方式定义接口来解决问题:
public interface IModelBuilder<out T> { }
out修饰符使得你的接口具有协变性,这将允许你从IStepModelBuilder<SpecificStepViewModel>转换为IStepModelBuilder<IStepViewModel>。需要注意的是,这也对你的接口施加了约束,它不允许定义任何以T作为参数的方法,只能作为返回值。
你可以在这里阅读更多关于协变性和逆变性的内容。 编辑 正如你在评论中提到的,你的接口可能看起来像这样:
public interface IModelBuilder<T>
{
    T Create(T myViewModel);
}

如果您不想将T作为参数传递给Create,而是可以传递IStepViewModel或其他任何不是T的内容,则这应该可以解决您的问题:
public interface IModelBuilder<out T>
{
    T Create(IStepViewModel myViewModel);
}

如果不行的话,那么你尝试转换类型的操作就不应该被允许。

我也看到了这个问题,我会添加接口,但是我认为我不能在这里使用协变性,因为我还将类型用作输入参数,据我所知,它需要是不变的? - M05Pr1mty
谢谢你,非常有用。既然是这种情况,有没有办法重新构建整个东西以实现我想要的目标? - M05Pr1mty
啊,我明白了 - 好吧,你是一个英雄,因为这个方法很有效,我将不得不把非特定类型传递到参数中并在内部进行强制转换!再次感谢。 - M05Pr1mty

2
我认为以下两个定义不兼容。
public interface IStepViewModelBuilderFactory
{
    IStepModelBuilder<T> Create<T>(T stepViewModel) where T : IStepViewModel;
    //... rest of the class definition
}

public class SpecificViewModelBuilder : IStepModelBuilder<SpecificStepViewModel>
{
}

当Create运行时,它将生成的类型(即SpecificViewModelBuilder)转换为其返回值,即IStepModelBuilder<T>
这是不可能的,您可以通过尝试手动执行此操作来进行测试:
class MyTest<T> where T : IStepViewModel
    {
        void Test()
        {
            IStepModelBuilder<T> cannotImplicitlyCast = new SpecificStepViewModelBuilder();
        }
    }

编辑:一些(可能不太好的)想法

这是可以做到的:

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

因此,您可以专门为每个SpecificStepViewModel设置工厂。


我同意 - 你有解决这个问题的任何建议吗?不知道是我看得太久了还是超出了我的能力范围! - M05Pr1mty
有趣的想法 - 我可能会在以后回来看看。也许可以将某种抽象工厂包装起来,以提供正确的特定工厂...无论如何感谢! - M05Pr1mty

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接