Dot-Net

VB.NET 實現多種逆變介面類型

  • September 3, 2017

基本問題: 給定一個介面:ICopiesFrom(Of In TModel),其中泛型參數沒有類型約束,該介面是否可以在沒有編譯器警告的情況下使用不同類型參數在同一個具體類型上多次實現?

**背景資訊:**由於Google的 Eric Lippert 先生和數小時的測試/實驗,我對協變和逆變的處理近年來一直在增加。在我正在進行的項目中,我需要分離架構的不同層,而不是將基本模型/實體類型暴露給更高層(表示)。為了實現這一點,我一直在創建複合類(MVC 模型),其中包含潛在的多個不同基礎層模型類型的各個方面。我有一個單獨的層,它將從基本類型(服務層)建構這些複合類型。一個重要的要求是基礎類型不能通過引用傳遞,因此必須複製屬性才能創建基礎模型類的深層副本。

為了從服務層中刪除一些冗長而醜陋的程式碼,我創建了一個介面,該介面定義了複合類型的通用協定,允許在復合對像中複製屬性值。然而,當我想多次實現這個介面時,VB 編譯器會生成一個警告。該程序執行得很好,但我想了解為什麼會發生這種情況的細節。特別是,如果這是一個脆弱或糟糕的設計決策,我現在想知道,在我深入之前。

環境細節:

  • 語言: VB.Net
  • .NET: 4.0
  • 開發環境: VS2010 SP1
  • **用法:**網站(MVC2)

為了弄清楚這一點,我對 SO 和網際網路進行了一些研究,但沒有真正解決我的問題。以下是我諮詢過的一些(但不是全部)資源:

**摘要:**有沒有更好/更清潔/更靈活的方法來實現我想要的,或者我必須忍受編譯器警告?

這是一個說明問題的可執行範例(不是實際程式碼):

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。

引用自:https://stackoverflow.com/questions/23913209