1

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

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

视图模型

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

Public MustInherit Class ViewModelBase
    Implements INotifyPropertyChanged, IDisposable

    ...

End Class

具体的 ViewModel 如下所示:

Class SalesOrderEntryViewModel
    Inherits ViewModelBase
    Implements IEditorViewModel, IChildViewModel

    ...

End Class

IEditorViewModel并且IChildViewModel是我的一些具体 ViewModel 实现的接口。

意见

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

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

基于上述SalesOrderEntryViewModelI 的具体视图定义为:

Class SalesOrderEntryPage
    Implements IView(Of SalesOrderEntryViewModel)

End Class

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

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

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

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

4

2 回答 2

5

您面临的问题与泛型的使用有关,并且与称为泛型类型参数变化的概念有关。泛型接口的类型参数可以是不变的、协变的或逆变的。

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

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

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

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


  1. 假设IView(Of T)有一个需要type 参数的 T方法:这意味着,它IView(Of SalesOrderEntryViewModel)有一个需要 type 参数的方法SalesOrderEntryViewModel。但是,如果您可以强制IView(Of SalesOrderEntryViewModel)转换为IView(Of ViewModelBase),您会期望它有一个带有 type 参数的方法ViewModelBase,但事实并非如此,因为该方法仅适用于 type 参数,SalesOrderEntryViewModel而不是其通用版本或其他派生自ViewModelBase. 结果:您不能将 IView(Of SalesOrderEntryViewModel) 转换为 IView(Of ViewModelBase)。

  1. 假设IView(Of T)有一个返回类型 T 的值的方法,并假设实现的类上的方法IView(Of ViewModelBase)将返回 a SalesOrderEntryViewModel,这没关系,因为SalesOrderEntryViewModel它是 的实例ViewModelBase。到目前为止,一切都很好。但是,如果您现在尝试将此类强制转换为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)

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

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

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

于 2013-03-15T08:21:01.733 回答
0

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

    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)
于 2013-03-15T13:20:56.607 回答