VB.NET 實現多種逆變介面類型
基本問題: 給定一個介面:ICopiesFrom(Of In TModel),其中泛型參數沒有類型約束,該介面是否可以在沒有編譯器警告的情況下使用不同類型參數在同一個具體類型上多次實現?
**背景資訊:**由於Google的 Eric Lippert 先生和數小時的測試/實驗,我對協變和逆變的處理近年來一直在增加。在我正在進行的項目中,我需要分離架構的不同層,而不是將基本模型/實體類型暴露給更高層(表示)。為了實現這一點,我一直在創建複合類(MVC 模型),其中包含潛在的多個不同基礎層模型類型的各個方面。我有一個單獨的層,它將從基本類型(服務層)建構這些複合類型。一個重要的要求是基礎類型不能通過引用傳遞,因此必須複製屬性才能創建基礎模型類的深層副本。
為了從服務層中刪除一些冗長而醜陋的程式碼,我創建了一個介面,該介面定義了複合類型的通用協定,允許在復合對像中複製屬性值。然而,當我想多次實現這個介面時,VB 編譯器會生成一個警告。該程序執行得很好,但我想了解為什麼會發生這種情況的細節。特別是,如果這是一個脆弱或糟糕的設計決策,我現在想知道,在我深入之前。
環境細節:
- 語言: VB.Net
- .NET: 4.0
- 開發環境: VS2010 SP1
- **用法:**網站(MVC2)
為了弄清楚這一點,我對 SO 和網際網路進行了一些研究,但沒有真正解決我的問題。以下是我諮詢過的一些(但不是全部)資源:
- 逆變解釋
- http://msdn.microsoft.com/en-us/library/dd799517(v=vs.110).aspx
- http://ericlippert.com/2013/07/29/a-contravariance-conundrum/#more-1344
**摘要:**有沒有更好/更清潔/更靈活的方法來實現我想要的,或者我必須忍受編譯器警告?
這是一個說明問題的可執行範例(不是實際程式碼):
Public Module Materials Sub Main() Dim materials As New List(Of Composite)() Dim materialData As New Dictionary(Of MaterialA, MaterialB)() 'Load data from a data source 'materialData = Me.DataService.Load(.....'Query parameters'.....) Dim specificMaterial As New SpecialB() With {.Weight = 24, .Height = 12} Dim specificMaterialDesc As New MaterialA() With {.Name = "Silly Putty", .Created = DateTime.UtcNow.AddDays(-1)} Dim basicMaterial As New MaterialB() With {.Weight = 34.2, .Height = 8} Dim basicMaterialDesc As New MaterialA() With {.Name = "Gak", .Created = DateTime.UtcNow.AddDays(-2)} materialData.Add(specificMaterialDesc, specificMaterial) materialData.Add(basicMaterialDesc, basicMaterial) For Each item In materialData Dim newMaterial As New Composite() newMaterial.CopyFrom(item.Key) newMaterial.CopyFrom(item.Value) materials.Add(newMaterial) Next Console.WriteLine("Total Weight: {0} lbs.", materials.Select(Function(x) x.Weight).Sum()) Console.ReadLine() End Sub End Module ''' <summary> ''' Class that represents a composite of two separate classes. ''' </summary> ''' <remarks></remarks> Public Class Composite Implements ICopiesFrom(Of MaterialA) Implements ICopiesFrom(Of MaterialB) #Region "--Constants--" Private Const COMPOSITE_PREFIX As String = "Comp_" #End Region #Region "--Instance Variables--" Private _created As DateTime Private _height As Double Private _name As String Private _weight As Double #End Region #Region "--Constructors--" ''' <summary> ''' Creates a new instance of the Composite class. ''' </summary> ''' <remarks></remarks> Public Sub New() _created = DateTime.MaxValue _height = 1D _name = String.Empty _weight = 1D End Sub #End Region #Region "--Methods--" Public Overridable Overloads Sub CopyFrom(ByVal model As MaterialA) Implements ICopiesFrom(Of MaterialA).CopyFrom If model IsNot Nothing Then Me.Name = model.Name Me.Created = model.Created End If End Sub Public Overridable Overloads Sub CopyFrom(ByVal model As MaterialB) Implements ICopiesFrom(Of MaterialB).CopyFrom If model IsNot Nothing Then Me.Height = model.Height Me.Weight = model.Weight End If End Sub #End Region #Region "--Functions--" Protected Overridable Function GetName() As String Dim returnValue As String = String.Empty If Not String.IsNullOrWhiteSpace(Me.Name) Then Return String.Concat(COMPOSITE_PREFIX, Me.Name) End If Return returnValue End Function #End Region #Region "--Properties--" Public Overridable Property Created As DateTime Get Return _created End Get Set(value As DateTime) _created = value End Set End Property Public Overridable Property Height As Double Get Return _height End Get Set(value As Double) If value > 0D Then _height = value End If End Set End Property Public Overridable Property Name As String Get Return Me.GetName() End Get Set(value As String) If Not String.IsNullOrWhiteSpace(value) Then _name = value End If End Set End Property Public Overridable Property Weight As Double Get Return _weight End Get Set(value As Double) If value > 0D Then _weight = value End If End Set End Property #End Region End Class ''' <summary> ''' Interface that exposes a contract / defines functionality of a type whose values are derived from another type. ''' </summary> ''' <typeparam name="TModel"></typeparam> ''' <remarks></remarks> Public Interface ICopiesFrom(Of In TModel) #Region "--Methods--" ''' <summary> ''' Copies a given model into the current instance. ''' </summary> ''' <param name="model"></param> ''' <remarks></remarks> Sub CopyFrom(ByVal model As TModel) #End Region End Interface Public Class MaterialA #Region "--Instance Variables--" Private _created As DateTime Private _name As String #End Region #Region "--Constructors--" ''' <summary> ''' Creates a new instance of the MaterialA class. ''' </summary> ''' <remarks></remarks> Public Sub New() _created = DateTime.MaxValue _name = String.Empty End Sub #End Region #Region "--Properties--" Public Overridable Property Created As DateTime Get Return _created End Get Set(value As DateTime) _created = value End Set End Property Public Overridable Property Name As String Get Return _name End Get Set(value As String) _name = value End Set End Property #End Region End Class Public Class MaterialB #Region "--Instance Variables--" Private _height As Double Private _weight As Double #End Region #Region "--Constructors--" ''' <summary> ''' Creates a new instance of the MaterialB class. ''' </summary> ''' <remarks></remarks> Public Sub New() _height = 0D _weight = 0D End Sub #End Region #Region "--Properties--" Public Overridable Property Height As Double Get Return _height End Get Set(value As Double) _height = value End Set End Property Public Overridable Property Weight As Double Get Return _weight End Get Set(value As Double) _weight = value End Set End Property #End Region End Class Public Class SpecialB Inherits MaterialB Public Overrides Property Weight As Double Get Return MyBase.Weight End Get Set(value As Double) MyBase.Weight = value * 2 End Set End Property End Class
該程序執行得很好,但我想了解發生這種情況的具體原因
存在警告是因為(通用)關於具有預先存在的繼承層次結構的介面和/或類的逆變性。它並不特別適用於您在範例中給出的情況(可能在您的真實程式碼中),但這就是警告的原因:
假設 MaterialB 繼承了 MaterialA,然後又被 SpecialB 繼承了
Public Class Composite Implements ICopiesFrom(Of MaterialA) Implements ICopiesFrom(Of MaterialB)結合
Public Interface ICopiesFrom(Of In TModel)說(由於’In’):Composite 可以是一個
ICopiesFrom(Of <anything Inheriting from MaterialA>)(有一個實現)並且 Composite 可以是一個ICopiesFrom(Of <anything Inheriting from MaterialB>)(有第二個實現)所以如果我說:
Dim broken As ICopiesFrom(Of SpecialB) = New Composite()應該選擇哪個實現,兩者都是有效的(選擇B似乎很自然,但它是模棱兩可的)
如果使用介面可能更清楚情況:
Public Class Composite2 Implements ICopiesFrom(Of IMaterialA) Implements ICopiesFrom(Of IMaterialB) ... Public Class Broken Implements IMaterialA Implements IMaterialB ... Dim broken As ICopiesFrom(Of Broken) = New Composite()編譯器現在應該使用哪個實現?
您的範例中也沒有任何內容需要In 關鍵字(可能在實際程式碼中可能存在)。除非您需要“繞過” Composite AS a
ICopiesFrom(Of SpecialB),例如您一無所獲,ICopiesFrom(Of MaterialB)否則可以通過正常(非通用)機制處理沒有(通用)逆變的 SpecialB。