Generics: List Of Class Implementing Generics

elroyskimms

Member
Joined
Jul 10, 2015
Messages
19
Programming Experience
10+
Sometimes I just can't wrap my head around Generics...

For the sake of simplicity, I've boiled everything down to a very simple example that you can copy and paste. I have a generic interface which is restricted to types that implement IComparable. I have a Class that Implements this interface. I create 3 different kinds of this Class using different Types that each implement IComparable (Integer, Long, and Decimal). I create a generic List to contain these 3 items and that's where I get the InvalidCastException. How can I create a List of items that all implement the same interface (and/or inherit from the same class)?

VB.NET:
Public Class SampleImplementation
    Public Sub RunMe()
        Dim Item1 As New Sample(Of Long) With {.Value = 424242, .strName = "Hello-Long"}
        Dim Item2 As New Sample(Of Integer) With {.Value = 42, .strName = "Hello-Integer"}
        Dim Item3 As New Sample(Of Decimal) With {.Value = 42.42, .strName = "Hello-Decimal"}
        Dim List1 As New List(Of interfaceSample(Of IComparable))
        List1.Add(Item1)
        List1.Add(Item2)
        List1.Add(Item3)
    End Sub
End Class

Public Interface interfaceSample(Of T As IComparable)
    Property Value As T
    Property strName As String
End Interface

Public Class Sample(Of T As IComparable)
    Implements interfaceSample(Of T)
    Public Property strName As String Implements interfaceSample(Of T).strName
    Public Property Value As T Implements interfaceSample(Of T).Value
End Class

The real code has a variety of methods and properties which require IComparable, but I've removed those just to keep this example short.
It's probably something simple, but my head hurts and I can't figure it out. Thank you for any suggestions you might have,

-E
 
Firstly, you should stick to the accepted convention when defining interfaces and use ISample to name the interface in your example.

As for your issue, the thing you're missing is that a generic class is actually not a single class but a range of classes. Likewise a generic interface is not a single interface but a range of interfaces. Your Sample(Of T) type is NOT a single type. It's an umbrella for many different types. Sample(Of Integer), Sample(Of Long) and Sample(Of Decimal) are three different types, so you can't create a List(Of T) where T is any concrete type that represents all three. Basically, what you want to do is not possible because it doesn't make sense.
 
I guess I am looking at it wrong as it makes sense to me. Defining a generic List(Of ...) with the same restrictions used in the generic class. In a way, it's like creating a List(Of Object) which can hold any kind of Class, because they all inherit from Object. I was viewing this as a widening operation. I understand why I wouldn't be able to add a Sample(Of String) to a List(Of Sample(Of Decimal)) because those are not the same. But if Sample(Of String) and Sample(Of Decimal) both meet the restriction of Sample(Of IComparable), I had assumed there would be a way to store them together in the same collection, assuming the collection was restricted to Sample(Of IComparable). Like I said, I must be looking at it wrong.

I came up with a solution. Essentially I created 2 Interfaces, one Generic and one not Generic. For my purposes, the properties included in the Generic type aren't needed in this part of my code. So I moved those properties into their own (properly named :) ) interface and defined the collection as List(Of INonGenericSample).

VB.NET:
Public Class SampleImplementation
    Public Sub RunMe()
        Dim Item1 As New Sample(Of Long) With {.Value = 424242, .Name = "Hello-Long"}
        Dim Item2 As New Sample(Of Integer) With {.Value = 42, .Name = "Hello-Integer"}
        Dim Item3 As New Sample(Of Decimal) With {.Value = 42.42, .Name = "Hello-Decimal"}
        Dim List1 As New List(Of INonGenericSample)
        List1.Add(Item1)
        List1.Add(Item2)
        List1.Add(Item3)
        For Each Item In List1
            'This works because Item.Name is part of the NonGeneric interface
            Dim ItemName As String = Item.Name
            'Lucky for me, the end result of this code doesn't need the Generic interface.
            'My problem is solved.
        Next
    End Sub
End Class 

Public Interface INonGenericSample
    Property Name As String
End Interface

Public Interface ISample(Of T As IComparable)
    Property Value As T
End Interface

Public Class Sample(Of T As IComparable)
    Implements ISample(Of T), INonGenericSample
    Public Property Name As String Implements INonGenericSample.Name
    Public Property Value As T Implements ISample(Of T).Value
End Class

If I absolutely had to access the Generic Value property within this list, I could always add a Value as Object property to INonGenericSample and then have the Get/Set methods point to the Generic Value property like this:

VB.NET:
Public Class frmCodeGenerator
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim Item1 As New Sample(Of Long) With {.Value = 424242, .Name = "Hello-Long"}
        Dim Item2 As New Sample(Of Integer) With {.Value = 42, .Name = "Hello-Integer"}
        Dim Item3 As New Sample(Of Decimal) With {.Value = 42.42, .Name = "Hello-Decimal"}
        Dim List1 As New List(Of INonGenericSample)
        List1.Add(Item1)
        List1.Add(Item2)
        List1.Add(Item3)
        For Each Item In List1
            Dim ItemName As String = Item.Name
            Dim ItemValue As Object = Item.Value
        Next
    End Sub
End Class

Public Interface INonGenericSample
    Property Name As String
    Property Value As Object
End Interface

Public Interface ISample(Of T As IComparable)
    Property Value As T
End Interface

Public Class Sample(Of T As IComparable)
    Implements ISample(Of T), INonGenericSample
    Public Property Name As String Implements INonGenericSample.Name
    Public Property Value As T Implements ISample(Of T).Value
    Private Property INonGenericSample_Value As Object Implements INonGenericSample.Value
        Get
            Return Value
        End Get
        Set(newValue As Object)
            Value = newValue
        End Set
    End Property
End Class

-E
 
I had assumed there would be a way to store them together in the same collection, assuming the collection was restricted to Sample(Of IComparable).

You absolutely could do that, but then you'd be able to store any other type that implements IComparable too. If you're OK with that then IComparable is the concrete type you should use in that case. There's no way to allow Long, Integer and Decimal and disallow every other type though, because there's no concrete type that represents just those three and no other.
 
That is what I was trying to do. I limited my sample to Long, Integer, and Decimal to keep it simple and reproduce the InvalidCastException. In production, the Sample(Of ...) generic class can be anything that implements IComparable. If List(Of ISample(Of IComparable)) isn't correct, what is the correct way to declare the list so it will hold any variation of Sample(Of IComparable)?

-E
 
Back
Top