Basic Question: Given an interface: ICopiesFrom(Of In TModel) where there is no type constraint on the generic argument, can that interface be implemented more than once on the same concrete type using a different type argument without a compiler warning?
Background Info: My handle on covariance and contravariance has been increasing in recent years thanks to Mr. Eric Lippert, Google, and many hours of testing / experimenting. In a project I am working on, I have a need to separate different layers of the architecture and not expose base model / entity types to a higher layer (presentation). To accomplish this, I have been creating composite classes (MVC Models) that contain aspects of potentially multiple different base layer model types. I have a separate layer that will build these composite types from the base types (service layer). One important requirement is that the base types not be passed up via a reference, so properties must be duplicated in order to create a deep-copy of the base model class.
To remove some of the lengthy and ugly code from the service layer, I created an interface that defines a common contract for composite types that allows for the property values to be copied in the composite object. When I want to implement this interface multiple times however, the VB compiler generates a warning. The program runs just fine, but I want to understanding the specifics of why this is happening. Particularly, if this is a fragile or poor design decision, I want to know now before I get too deep.
Environment Details:
- Language: VB.Net
- .NET: 4.0
- IDE: VS2010 SP1
- Usage: Website (MVC2)
In attempting to figure this out, I have done some reasearch on SO and the internet but nothing really addresses my question specifically. Here are some of (but not all) the resources I have consulted:
- Contravariance explained
- http://msdn.microsoft.com/en-us/library/dd799517(v=vs.110).aspx
- http://ericlippert.com/2013/07/29/a-contravariance-conundrum/#more-1344
Summary: Is there a better / cleaner / more flexible way to achieve what I'm trying to or do I have to live with the compiler warning?
Here is a run-able example (not the actual code) that illustrates the issue:
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