这是MVVM + WPF,但实际上与这些工具没有多大关系。该问题更具有通用性,属于面向对象设计。
昨天,MarcinJuraszek帮助我向original problem提出了一个很好的解决方案。那个解决方案解决了手头的问题,但现在我仍停留在下一个层次上。事情是这样的:
ViewModels
我的ViewModel类继承自一个公共的抽象父ViewModel类:
Public MustInherit Class ViewModelBase
Implements INotifyPropertyChanged, IDisposable
...
End Class混凝土ViewModels是这样的:
Class SalesOrderEntryViewModel
Inherits ViewModelBase
Implements IEditorViewModel, IChildViewModel
...
End ClassIEditorViewModel和IChildViewModel是我的一些具体ViewModels实现的接口。
视图
所有my类都实现了以下接口:
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正在帮助我开发后期绑定,但我想知道为什么会这么说,在这方面,什么是优雅的解决方案呢?
发布于 2013-03-15 08:21:01
您所面临的问题与泛型的使用有关,并且与泛型类型参数的方差概念有关。泛型接口的类型参数可以是不变的、协变的或反变的。
默认情况下,泛型接口在它的类型T中是不变的。这意味着,您可以使用T作为方法参数的类型,也可以作为方法的返回类型。在不利方面,这意味着以下两种情况都不可能发生:
铸造IView(Of ViewModelBase)到IView(指SalesOrderEntryViewModel) 铸造IView(Of SalesOrderEntryViewModel)到IView(指ViewModelBase)
这是有意义的,当你考虑:
IView(Of T)有一个方法需要一个T类型的参数:这意味着IView(Of SalesOrderEntryViewModel)有一个方法,它需要一个SalesOrderEntryViewModel类型的参数。但是,如果您可以将IView(Of SalesOrderEntryViewModel)转换为IView(Of ViewModelBase),您可能会期望它有一个参数为ViewModelBase类型的方法,但它没有,因为该方法只针对SalesOrderEntryViewModel类型的参数,而不是针对其广义版本的参数或从ViewModelBase派生的其他ViewModels。其结果是:您不能将IView(Of SalesOrderEntryViewModel)转换为IView(Of ViewModelBase).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或者在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中协变。因此,您失去了通过ViewModel接口设置IView的能力。
所以,这并不能真正解决问题,但我希望它能让你明白,为什么它不起作用,你想要做什么。
我建议为您的所有视图定义一个反向的或非非通用的基础接口。我做了后者,虽然它不包含太多(非IView(模型)接口),但它使生活变得更容易了。然后派生一个允许设置视图的ViewModel的接口。通过这种方式,您可以使用视图的readonly版本填充集合。
发布于 2013-03-15 13:20:56
对于任何陷入这种情况并愿意进行反射的其他人,下面是一个使用反射信息来实现这一目标的帮助方法:
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)https://stackoverflow.com/questions/15424349
复制相似问题