【问题标题】:Why is not possible to cast IView(Of SpecificViewModel) to IView(Of ViewModelBase)?为什么不能将 IView(Of SpecificViewModel) 转换为 IView(Of ViewModelBase)?
【发布时间】:2013-03-03 16:36:28
【问题描述】:

这是 MVVM + WPF,但实际上与这些工具没有太大关系。问题比较笼统,属于OO设计。

昨天 MarcinJuraszek 帮助我向original problem 提出了一个出色的解决方案。该解决方案解决了手头的问题,但现在我陷入了下一个层次。事情是这样的:

视图模型

我的 ViewModel 类继承自一个通用的抽象父 ViewModel 类:

Public MustInherit Class ViewModelBase
    Implements INotifyPropertyChanged, IDisposable

    ...

End Class

具体的 ViewModel 如下所示:

Class SalesOrderEntryViewModel
    Inherits ViewModelBase
    Implements IEditorViewModel, IChildViewModel

    ...

End Class

IEditorViewModelIChildViewModel 是我的一些具体 ViewModel 实现的接口。

观看次数

我所有的 View 类都实现了以下接口:

Interface IView(Of T As ViewModelBase)
    WriteOnly Property MyVM As T
    ReadOnly Property HeaderText As String
End Interface

基于我上面描述的SalesOrderEntryViewModel 的具体视图定义为:

Class SalesOrderEntryPage
    Implements IView(Of SalesOrderEntryViewModel)

End Class

到目前为止一切顺利。我现在面临的实际问题是我想在应用程序级别创建所有打开的视图的强类型集合。这个集合的类型应该是什么?我尝试了类似以下的方法:

Dim Views As List(Of IView(Of ViewModelBase))

当我尝试将 SalesOrderEntryPage 类的对象添加到此列表时,它会引发运行时异常,告诉我它无法从 SalesOrderEntryPage 转换为 IView(Of ViewModelBase),尽管查看定义 SalesOrderEntryPage真的是IView(Of ViewModelBase)

目前,VB.NET 正在帮助我解决后期绑定问题,但我想知道为什么会这样说,在这方面有什么优雅的解决方案?

【问题讨论】:

    标签: vb.net generics mvvm interface casting


    【解决方案1】:

    您面临的问题与泛型的使用有关,并且与泛型类型参数的一个称为variance 的概念有关。泛型接口的类型参数可以是invariantcovariantcontravariant

    默认情况下,通用接口的类型 T 是不变的。这意味着,您可以使用 T 作为方法参数的类型和方法的返回类型。不利的一面是,这意味着以下任何一项都不可能:

    将 IView(Of ViewModelBase) 转换为 IView(Of SalesOrderEntryViewModel)

    将 IView(Of SalesOrderEntryViewModel) 转换为 IView(Of ViewModelBase)

    这是有道理的,当您考虑:


    1. 假设IView(Of T) 有一个方法需要参数类型 T:这意味着IView(Of SalesOrderEntryViewModel) 有一个方法需要SalesOrderEntryViewModel 类型的参数。但是如果你可以将IView(Of SalesOrderEntryViewModel) 转换为IView(Of ViewModelBase),你会期望它有一个带有ViewModelBase 类型参数的方法,但它没有,因为该方法是为SalesOrderEntryViewModel 类型的参数设计的,并且不是它的通用版本或派生自ViewModelBase 的其他 ViewModel。结果:您不能将 IView(Of SalesOrderEntryViewModel) 转换为 IView(Of ViewModelBase)。

    1. 假设IView(Of T) 有一个返回类型为T 的值的方法,并假设实现IView(Of ViewModelBase) 的类上的方法将返回SalesOrderEntryViewModel,这是可以的,因为SalesOrderEntryViewModelViewModelBase 的一个实例。到目前为止,一切都很好。但是,如果您现在尝试将此类强制转换为IView(Of SomeOtherViewModel,它就不再起作用了,因为您的方法想要返回的类型SalesOrderEntryViewModel 不是SomeOtherViewModel 的实例。结果:您不能将 IView(Of ViewModelBase) 转换为 IView(Of SalesOrderEntryViewModel)。

    但是:

    有一种方法可以绕过这些限制之一,但您必须选择:

    您可以在T 中使您的接口逆变:这意味着您不能使用 T 作为您的方法的返回类型,但您可以将 Interface(Of Base) 转换为 Interface(Of Derived)

    Interface IContravariant(Of In A)
        Sub SetSomething(ByVal sampleArg As A)
        Sub DoSomething(Of T As A)()
        ' The following statement generates a compiler error. 
        ' Function GetSomething() As A 
    End Interface
    

    或者你在T 中使你的接口协变:那么你不能有接受T 类型参数的方法,但是你可以将Interface(Of Derived) 转换为Interface(Of Base)

    Interface ICovariant(Of Out R)
        ' The following statement generates a compiler error 
        ' because you can use only contravariant or invariant types 
        ' in generic contstraints. 
        ' Sub DoSomething(Of T As R)() 
    End Interface
    

    这就是理论。


    针对您的特殊情况:

    当我尝试将 SalesOrderEntryPage 类的对象添加到此列表时, 它抛出一个运行时异常,告诉我它不能从 SalesOrderEntryPage 到 IView(Of ViewModelBase)

    所以,你想从接口(派生)转换为接口(基础)。这意味着,您需要在其 ViewModelType 中使您的接口 IView 协变。因此,您失去了通过 IView 接口设置 ViewModel 的能力。

    所以,它并没有真正解决问题,但我希望它能让你清楚,为什么它不起作用,你想要做什么。

    我建议为所有视图定义一个逆变或非泛型基本接口。我做了后者,虽然它包含的内容不多(非泛型 IView(Model) 接口),但它让生活变得更轻松。然后派生一个允许设置 View 的 ViewModel 的接口。这样您就可以使用您的视图的readonly 版本填充您的集合。

    【讨论】:

    • 感谢您的详尽解释。问题是我的通用参数被用来在我的 ViewModel 类中定义一个属性(Get 和 Set),所以从我从你的回答中了解到,我同时需要协变和反变行为,据我了解这是不可能的。为此,我最终做了反射。很快就会发布。但这绝对是一般情况下的正确答案。
    【解决方案2】:

    对于陷入这种情况并愿意进行反射的任何其他人,这里有一个使用反射信息来实现此目的的辅助方法:

        Private Function ImplementsGenericInterface(type As Type, interfaceName As String, genericArgTypeName As String) As Boolean
        Dim myFilter As New Reflection.TypeFilter(Function(typeObj As Type, criteriaObj As [Object])
                                                      Return typeObj.ToString().StartsWith(interfaceName)
                                                  End Function)
    
        Dim MyIViewInterfaces() As System.[Type] = type.FindInterfaces(myFilter, "")
    
        If MyIViewInterfaces.Length > 0 Then
            Return MyIViewInterfaces.Any(Function(x) x.GetGenericArguments().Any(Function(y) y.BaseType.Name = genericArgTypeName))
        End If
    
        Return False
    End Function
    

    对于我在问题中发布的示例,请像这样使用它:

    ImplementsGenericInterface(objSalesOrderEntryPage.GetType(), "ProjectNamespace. Name", GetType(ViewModelBase).Name)
    

    【讨论】:

      猜你喜欢
      • 2012-11-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-23
      • 2023-02-02
      • 1970-01-01
      相关资源
      最近更新 更多