【问题标题】:VB.NET Implement Multiple Contravariant interface typesVB.NET 实现多种逆变接口类型
【发布时间】:2014-05-28 13:37:26
【问题描述】:

基本问题: 给定一个接口:ICopiesFrom(Of In TModel) 对泛型参数没有类型约束,该接口是否可以使用不同的具体类型在同一个具体类型上实现多次没有编译器警告的类型参数?

背景信息:近年来,得益于 Google 的 Eric Lippert 先生以及大量的测试/实验,我对协变和逆变的处理能力不断提高。在我正在进行的项目中,我需要分离架构的不同层,而不是将基本模型/实体类型暴露给更高层(表示)。为了实现这一点,我一直在创建复合类(MVC 模型),其中包含潜在的多个不同基础层模型类型的各个方面。我有一个单独的层,它将从基本类型(服务层)构建这些复合类型。一个重要的要求是基类型不能通过引用传递,因此必须复制属性才能创建基模型类的深层副本。

为了从服务层删除一些冗长而丑陋的代码,我创建了一个接口,该接口定义了复合类型的通用协定,允许将属性值复制到复合对象中。然而,当我想多次实现这个接口时,VB 编译器会生成一个警告。该程序运行得很好,但我想了解为什么会发生这种情况的细节。特别是,如果这是一个脆弱或糟糕的设计决策,我想在深入了解之前立即了解。

环境详情:

  • 语言: VB.Net
  • .NET: 4.0
  • IDE: 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

【问题讨论】:

  • 根据Eric Lippert's post 这似乎是不必要的行为。你对此无能为力。
  • '不将基本模型/实体类型暴露给更高层'和'基本类型不会通过引用传递'只是我的意见而不是答案,但我认为这个决定是你的原因问题。往上一层他们应该变得更加抽象,但是迫使较低的级别不可访问意味着您正在有效地复制每个级别所需的代码,并且消除重复是您应该考虑的重要设计考虑因素之一强调。
  • @ClaraOnager 我明白你的意思。目前,我正试图避免“脆弱的基类”问题并解决 SRP 问题。 'higher level' 类型将用于显示/视图,因此其他'lower level' 对象可以根据 DB 模式进行更改,而不会影响'display level' 代码。这与继承“低级”类或将其添加为“高级”类的字段/属性相反。此外,“较低级别”类中的某些字段可能在我不想在此处访问的其他程序中使用。这有意义吗?
  • @NicoSchertler 您对实现这一目标的替代方法有什么建议吗?或者我应该弃船吗?
  • 我看到并且巧合的是我遇到了类似的问题。我认为的另一件事是,如果复合类包含而不是实现可能会更好。即,与其说是“是”,不如说是“有”。包含两个其他类的实例的类在概念上比实现其他两个类的接口的类要简单得多,因此更容易在更高级别上使用。

标签: .net vb.net generics architecture contravariance


【解决方案1】:

程序运行得很好,但我想了解细节 为什么会发生这种情况

警告是因为(通用)关于具有预先存在的继承层次结构的接口和/或类的逆变性。它并不特别适用于您在示例中给出的情况(可能在您的真实代码中),但这就是警告的原因:

假设 MaterialB 继承了 MaterialA,然后又被 SpecialB 继承了

Public Class Composite
Implements ICopiesFrom(Of MaterialA)
Implements ICopiesFrom(Of MaterialB)

结合

Public Interface ICopiesFrom(Of In TModel)

说(由于'In'): Composite 可以是ICopiesFrom(Of &lt;anything Inheriting from MaterialA&gt;)(有一个实现) 和 Composite 可以是ICopiesFrom(Of &lt;anything Inheriting from MaterialB&gt;)(带有第二个实现)

如果我说:

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。

【讨论】:

    猜你喜欢
    • 2011-10-26
    • 1970-01-01
    • 2018-05-21
    • 1970-01-01
    • 1970-01-01
    • 2016-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多