VB.NET реализовать несколько контравариантных типов интерфейса
Основной вопрос: При наличии интерфейса: ICopiesFrom(Of In TModel), где нет ограничения по типу для универсального аргумента, может ли этот интерфейс быть реализован более одного раза для одного и того же конкретного типа с использованием аргумента другого типа без предупреждения компилятора?
Справочная информация: Мои знания о ковариации и контравариантности в последние годы увеличиваются благодаря г-ну Эрику Липперту, Google, и многим часам тестирования / экспериментов. В проекте, над которым я работаю, мне необходимо разделить разные уровни архитектуры и не предоставлять базовую модель / типы сущностей более высокому уровню (представлению). Для этого я создавал составные классы (модели MVC), которые содержат аспекты потенциально нескольких типов моделей базового уровня. У меня есть отдельный слой, который будет создавать эти составные типы из базовых типов (уровень обслуживания). Одним из важных требований является то, что базовые типы не должны передаваться через ссылку, поэтому свойства должны быть продублированы для создания глубокой копии класса базовой модели.
Чтобы удалить часть длинного и уродливого кода из сервисного уровня, я создал интерфейс, который определяет общий контракт для составных типов, который позволяет копировать значения свойств в составном объекте. Однако когда я хочу реализовать этот интерфейс несколько раз, компилятор VB выдает предупреждение. Программа работает просто отлично, но я хочу понять особенности того, почему это происходит. В частности, если это хрупкое или плохое дизайнерское решение, я хочу знать сейчас, прежде чем углубиться.
Окружающая среда Подробности:
- Язык: VB.Net
- .NET: 4.0
- IDE: 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/
Резюме: есть ли лучший / чище / более гибкий способ достичь того, что я пытаюсь сделать, или мне нужно жить с предупреждением компилятора?
Вот пример запуска (не фактический код), который иллюстрирует проблему:
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 без (универсальной) контравариантности, используя обычные (неуниверсальные) механизмы.