为什么不能将IView(Of SpecificViewModel)转换为IView(Of ViewModelBase)?

dot*_*NET 1 vb.net generics casting interface mvvm

这是MVVM + WPF,但实际上与这些工具没什么关系.问题更通用,属于OO设计.

昨天MarcinJuraszek通过为原始问题提出了一个很好的解决方案来帮助我.该解决方案解决了手头的问题,但现在我陷入了下一个阶段.这是怎么回事:

的ViewModels

我的ViewModel类继承自公共抽象父ViewModel类:

Public MustInherit Class ViewModelBase
    Implements INotifyPropertyChanged, IDisposable

    ...

End Class
Run Code Online (Sandbox Code Playgroud)

具体的ViewModel是这样的:

Class SalesOrderEntryViewModel
    Inherits ViewModelBase
    Implements IEditorViewModel, IChildViewModel

    ...

End Class
Run Code Online (Sandbox Code Playgroud)

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

查看

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

Interface IView(Of T As ViewModelBase)
    WriteOnly Property MyVM As T
    ReadOnly Property HeaderText As String
End Interface
Run Code Online (Sandbox Code Playgroud)

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

Class SalesOrderEntryPage
    Implements IView(Of SalesOrderEntryViewModel)

End Class
Run Code Online (Sandbox Code Playgroud)

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

Dim Views As List(Of IView(Of ViewModelBase))
Run Code Online (Sandbox Code Playgroud)

当我尝试将SalesOrderEntryPage类的对象添加到此列表时,它会抛出一个运行时异常,告诉我它无法转换SalesOrderEntryPageIView(Of ViewModelBase),虽然查看定义,但SalesOrderEntryPage实际上是一个IView(Of ViewModelBase).

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

Mar*_*arc 5

您遇到的问题与泛型的使用有关,并且与泛型类型参数的方差有关.泛型接口的类型参数可以是不变的,协变的逆变的.

默认情况下,通用接口在其类型T中是不变的.这意味着,您可以使用T作为方法参数的类型,也可以使用返回类型的方法.在缺点方面,这意味着以下两种情况都不可能:

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

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

当你考虑时,这是有道理的:


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

  1. 假设IView(Of T)有一个方法,它返回一个类型为T的值,并假设一个实现的类的方法IView(Of ViewModelBase)将返回一个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
Run Code Online (Sandbox Code Playgroud)

或者你让你的界面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
Run Code Online (Sandbox Code Playgroud)

这就是理论.


对于您的特殊情况:

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

因此,您希望从接口(Of Derived)转换为Interface(Of Base).这意味着,您需要IView在ViewModelType 中使您的界面协变.因此,您无法通过IView界面设置ViewModel .

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

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