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

SalesOrderEntryViewModel上記の I に基づく具体的な View は、次のように定義されます。

Class SalesOrderEntryPage
    Implements IView(Of SalesOrderEntryViewModel)

End Class

ここまでは順調ですね。私が今直面している実際の問題は、アプリケーション レベルで開いているすべてのビューの厳密に型指定されたコレクションを作成したいということです。このコレクションの型は何ですか? 私は次のようなものを試しました:

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

クラスのオブジェクトをこのリストに追加しようとすると、からにSalesOrderEntryPage変換できないことを示す実行時例外がスローされます。定義を見ても、実際には.SalesOrderEntryPageIView(Of ViewModelBase)SalesOrderEntryPageIView(Of ViewModelBase)

今のところ、VB.NET は遅延バインディングで私を助けてくれていますが、なぜそう言っているのか、また、この点に関してエレガントな解決策は何なのか疑問に思っています。

4

2 に答える 2

5

あなたが直面している問題は、ジェネリックの使用に関連しており、ジェネリック型パラメーターの分散と呼ばれる概念に関係しています。ジェネリック インターフェイスの型パラメーターは、invariantcovariant、またはcontravariantにすることができます。

デフォルトでは、ジェネリック インターフェイスは T 型で不変です。つまり、T をメソッドのパラメーターの型として、またメソッドの戻り値の型としても使用できます。欠点として、これは次のいずれも可能でないことを意味します。

IView(Of ViewModelBase) を IView(Of SalesOrderEntryViewModel) にキャストします。

IView(Of SalesOrderEntryViewModel) を IView(Of ViewModelBase) にキャストします。

これは、次のことを考慮すると理にかなっています。


  1. タイプ のパラメーターIView(Of T)を必要とするメソッドがあると仮定: これは、タイプ のパラメーターを必要とするメソッドがあることを意味します。しかし、 にキャストできる場合は、 type のパラメーターを持つメソッドがあると予想されますが、そうではありません。メソッドは typeのみのパラメーター用に設計されており、一般化されたバージョンや から派生した他の ViewModel用には設計されていないためです。結果: IView(Of SalesOrderEntryViewModel) を IView(Of ViewModelBase) にキャストできません。 TIView(Of SalesOrderEntryViewModel)SalesOrderEntryViewModelIView(Of SalesOrderEntryViewModel)IView(Of ViewModelBase)ViewModelBaseSalesOrderEntryViewModelViewModelBase

  1. タイプ T の値を返すメソッドIView(Of T)があり、 を実装するクラスのメソッドが を返すと仮定します。これは のインスタンスであるため問題ありません。ここまでは順調ですね。しかし、このクラスを にキャストしようとすると、メソッドが返したい型が のインスタンスではないため、機能しなくなります。結果: IView(Of ViewModelBase) を IView(Of SalesOrderEntryViewModel) にキャストすることはできません。IView(Of ViewModelBase)SalesOrderEntryViewModelSalesOrderEntryViewModelViewModelBaseIView(Of SomeOtherViewModelSalesOrderEntryViewModelSomeOtherViewModel

しかし:

これらの制約の 1 つを回避する方法はありますが、次のいずれかを選択する必要があります。

でインターフェイスを反変にすることができます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) に変換できないことが通知されます。

したがって、interface(Of Derived) から Interface(Of Base) にキャストする必要があります。IViewつまり、 ViewModelType でインターフェイスを共変にする必要があります。したがって、インターフェイスを介して ViewModel を設定する機能が失われIViewます。

したがって、実際には問題が解決するわけではありませんが、なぜ機能しないのか、何をしようとしているのかが明確になることを願っています.

すべてのビューに対して、反変または非ジェネリックの基本インターフェイスを定義することをお勧めします。私は後者を使用しましたが、あまり含まれていませんが (非ジェネリックの 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 に答える