首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么不能将IView(Of SpecificViewModel)转换为IView(Of ViewModelBase)?

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

Stack Overflow用户
提问于 2013-03-15 03:49:27
回答 2查看 168关注 0票数 1

这是MVVM + WPF,但实际上与这些工具没有多大关系。该问题更具有通用性,属于面向对象设计。

昨天,MarcinJuraszek帮助我向original problem提出了一个很好的解决方案。那个解决方案解决了手头的问题,但现在我仍停留在下一个层次上。事情是这样的:

ViewModels

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

代码语言:javascript
复制
Public MustInherit Class ViewModelBase
    Implements INotifyPropertyChanged, IDisposable

    ...

End Class

混凝土ViewModels是这样的:

代码语言:javascript
复制
Class SalesOrderEntryViewModel
    Inherits ViewModelBase
    Implements IEditorViewModel, IChildViewModel

    ...

End Class

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

视图

所有my类都实现了以下接口:

代码语言:javascript
复制
Interface IView(Of T As ViewModelBase)
    WriteOnly Property MyVM As T
    ReadOnly Property HeaderText As String
End Interface

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

代码语言:javascript
复制
Class SalesOrderEntryPage
    Implements IView(Of SalesOrderEntryViewModel)

End Class

到目前一切尚好。我现在面临的实际问题是,我希望在应用程序级别创建一个所有开放视图的强类型集合。这个收藏的类型应该是什么?我尝试了以下几种方法:

代码语言:javascript
复制
Dim Views As List(Of IView(Of ViewModelBase))

当我试图将SalesOrderEntryPage类的一个对象添加到这个列表中时,它会抛出一个运行时异常,告诉我它不能从SalesOrderEntryPage转换为IView(Of ViewModelBase),尽管从定义上看,SalesOrderEntryPage实际上是一个IView(Of ViewModelBase)

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

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2013-03-15 08:21:01

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

默认情况下,泛型接口在它的类型T中是不变的。这意味着,您可以使用T作为方法参数的类型,也可以作为方法的返回类型。在不利方面,这意味着以下两种情况都不可能发生:

铸造IView(Of ViewModelBase)到IView(指SalesOrderEntryViewModel) 铸造IView(Of SalesOrderEntryViewModel)到IView(指ViewModelBase)

这是有意义的,当你考虑:

  1. 假设IView(Of T)有一个方法需要一个T类型的参数:这意味着IView(Of SalesOrderEntryViewModel)有一个方法,它需要一个SalesOrderEntryViewModel类型的参数。但是,如果您可以将IView(Of SalesOrderEntryViewModel)转换为IView(Of ViewModelBase),您可能会期望它有一个参数为ViewModelBase类型的方法,但它没有,因为该方法只针对SalesOrderEntryViewModel类型的参数,而不是针对其广义版本的参数或从ViewModelBase派生的其他ViewModels。其结果是:您不能将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)

代码语言:javascript
复制
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)

代码语言:javascript
复制
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版本填充集合。

票数 5
EN

Stack Overflow用户

发布于 2013-03-15 13:20:56

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

代码语言:javascript
复制
    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

对于我在问题中发布的例子,请如下所示:

代码语言:javascript
复制
ImplementsGenericInterface(objSalesOrderEntryPage.GetType(), "ProjectNamespace. Name", GetType(ViewModelBase).Name)
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/15424349

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档