VB.NET реализовать несколько контравариантных типов интерфейса

Основной вопрос: При наличии интерфейса: ICopiesFrom(Of In TModel), где нет ограничения по типу для универсального аргумента, может ли этот интерфейс быть реализован более одного раза для одного и того же конкретного типа с использованием аргумента другого типа без предупреждения компилятора?

Справочная информация: Мои знания о ковариации и контравариантности в последние годы увеличиваются благодаря г-ну Эрику Липперту, Google, и многим часам тестирования / экспериментов. В проекте, над которым я работаю, мне необходимо разделить разные уровни архитектуры и не предоставлять базовую модель / типы сущностей более высокому уровню (представлению). Для этого я создавал составные классы (модели 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

1 ответ

Решение

Программа работает просто отлично, но я хочу понять особенности того, почему это происходит

Предупреждение появляется из-за (общей) противоречивости в отношении интерфейсов и / или классов с уже существующей иерархией наследования. Это не относится конкретно к случаю, который вы дали в своем примере (возможно, в вашем реальном коде), но вот почему это предупреждение:

предположим, что MaterialB унаследовал MaterialA и был в свою очередь унаследован SpecialB тогда

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

в сочетании с

Public Interface ICopiesFrom(Of In TModel)

говорит (из-за "В"): композит может быть 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 ICopiesFrom(Of SpecialB) например, вы ничего не получаете, ICopiesFrom(Of MaterialB) может справиться со SpecialB без (универсальной) контравариантности, используя обычные (неуниверсальные) механизмы.

Другие вопросы по тегам