2013-03-15 52 views
1

這是MVVM + WPF,但實際上與這些工具沒有多大關係。問題更通用,屬於面向對象的設計。爲什麼不能將IView(Of SpecificViewModel)投影到IView(Of ViewModelBase)?

昨天MarcinJuraszek幫我提出了一個很好的解決方案original problem。該解決方案解決了問題,但現在我陷入了下一個層面。這是怎麼一回事呢:

的ViewModels

我的視圖模型類,從一個共同的抽象父ViewModel類繼承:

Public MustInherit Class ViewModelBase 
    Implements INotifyPropertyChanged, IDisposable 

    ... 

End Class 

混凝土的ViewModels是這樣的:

Class SalesOrderEntryViewModel 
    Inherits ViewModelBase 
    Implements IEditorViewModel, IChildViewModel 

    ... 

End Class 

IEditorViewModelIChildViewModel是我的一些具體ViewModel實現的接口。

查看

我所有的視圖類實現以下接口:

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類的對象添加到這個列表,它拋出一個運行時異常告訴我它無法轉換從SalesOrderEntryPageIView(Of ViewModelBase),雖然看定義,SalesOrderEntryPage確實是IView(Of ViewModelBase)

現在,VB.NET正在幫我解決後期綁定問題,但我想知道爲什麼它這麼說,以及這方面的優雅解決方案是什麼?

回答

5

您面臨的問題與泛型的使用有關,並且與泛型類型參數的方差有關。通用接口的類型參數可以是不變式,協變式逆變式

默認情況下,您的通用接口在類型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類型的參數,而不是其通用版本或其他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 

或者你讓你的界面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)

所以,你想從接口(Of Derived)轉換到Interface(Of Base)。這意味着,您需要在您的ViewModelType中使您的界面IView協變。因此,您無法通過您的IView界面設置ViewModel。

所以,它並沒有真正解決問題,但我希望它能夠告訴你,爲什麼它不工作,你正在做什麼。

我會建議爲所有視圖定義一個逆變或非泛型基接口。我做了後者,雖然它不包含太多(非通用的IView(模型)接口),但它使生活變得更容易。然後派生一個接口,允許設置View的ViewModel。通過這種方式,您可以使用視圖的readonly版本填充您的收藏。

+0

感謝您的詳細解釋。問題是我的泛型參數被用來在我的ViewModel類中定義一個屬性(Get和Set),所以從我從你的答案中理解的是,我同時需要同時和反變量行爲,據我瞭解是不可能的。爲此我最終做了反思。很快就會發布。但這絕對是一般情況下的正確答案。 – dotNET 2013-03-15 13:11:43

0

爲別人誰得到被困在這種情況下,願意做思考,這是一個使用反射信息來實現這樣的helper方法:

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) 
相關問題