Generics, Inheritance and Directcast

Hello,

why can't i cast a generic instance to another variable in which the type paramters in the of clause inherit from the type parameters in the source variable?
I think there is an inheritance relationship?

Markus
Example:

Public MustInherit Class clsBase
Public Overridable ReadOnly Property Name() As String
Get
Return "This is Base."
End Get
End Property
End Class


Public Class clsClass1
Inherits clsBase

Public Overrides ReadOnly Property Name() As String
Get
Return "This is Class1."
End Get
End Property

Public Remarks As String = "Class1 Remarks..."
End Class


Public Class clsClass2
Inherits clsBase

Public Overrides ReadOnly Property Name() As String
Get
Return "This is Class2."
End Get
End Property

Public Remarks As String = "Class2 Remarks..."
End Class


Public Class clsBaseCon(Of ObjFrom As clsBase, _
ObjTo As clsBase)

Private mBaseFrom As ObjFrom
Private mBaseTo As ObjTo

Public Property BaseFrom() As ObjFrom
Get
Return mBaseFrom
End Get
Set(ByVal value As ObjFrom)
mBaseFrom = value
End Set
End Property

Public Property BaseTo() As ObjTo
Get
Return mBaseTo
End Get
Set(ByVal value As ObjTo)
mBaseTo = value
End Set
End Property
End Class

Private Sub Test
Dim Con1 As New clsBaseCon(Of clsBase, clsBase)
Con1.BaseFrom = New clsClass1
Con1.BaseTo = New clsClass2

Dim Con2 As clsBaseCon(Of clsClass1, clsClass1)

> Error is here: Cannot convert...
Con2 = DirectCast(Con1, clsBaseCon(Of clsClass1, clsClass1))

MsgBox(Con2.BaseTo.Remarks)
End Sub

[1799 byte] By [MarkusSch.] at [2008-2-20]
# 1

Unfortunately, this is not true. I have tried to show why in the short code snippet below:



Module
Module1
Public Class Foo
End Class

Public Class Bar
Inherits Foo
End Class

Public Sub AddFoo(ByVal fooList As List(Of Foo))
fooList.Add(
New Foo)
End Sub

Sub Main()
Dim barList As New List(Of Bar)

' This will not work, which is a good thing...
Dim fooList As List(Of Foo) = DirectCast(barList, List(Of Foo))

AddFoo(fooList)
End Sub
End
Module

If the DirectCast were to succeed, one of two things could happen in the call to AddFoo:

1) A runtime exception will be thrown (since the instance being passed in is in fact a List(Of Bar) which isn't happy about someone adding a Foo to it)

2) Adding the Foo instance would succeed. Now, anyone who is using the barList and assuming that all the instances in it are of of type Bar (which is a reasonable assumption) may have problems.

Both of these scenarios will generate a run-time error, which is something that the typing system is trying very hard to prevent... which is it will not allow you to do this.

Best regards,
Johan Stenberg

JohanStenberg at 2007-9-9 > top of Msdn Tech,Visual Basic,Visual Basic Language...
# 2

Hello Johan,

i thought that DirectCast will run like before.
Have modified your code to show what i miss.

Regards,
Markus
Module Module1
Public Class Foo
End Class

Public Class Bar
Inherits Foo
End Class

Public Sub AddFoo(ByVal fooList As List(Of Foo))
fooList.Add(New Foo)
End Sub

Sub Main()
Dim FooList As New List(Of Foo)

' You can assign a Bar to a Foo because there is a inheritance relationship.
Dim Foo As Foo = New Bar

' You can Add Bars to the FooList because they inherit from Foo.
FooList.Add(Foo)
FooList.Add(New Bar)

' The FooList only contains Bars.
Dim Barlist As New List(Of Bar)

' You can cast a Foo Variable to a Bar Variable if you know that it contains
' a Bar Instance.
Dim Bar As Bar = DirectCast(Foo, Bar)

' But you cannot make this with a generic type?

' Error 2 Value of type 'System.Collections.Generic.List _
' (Of WindowsApplication1.Module1.Foo)' cannot be converted
' to 'System.Collections.Generic.List(Of WindowsApplication1.Module1.Bar)'

' The programmer know and is responsible that the Foolist only contains bars.
' It's the same when make Dim Bar As Bar = DirectCast(Foo, Bar).
Barlist = DirectCast(FooList, List(Of Bar))

End Sub
End Module

MarkusSch. at 2007-9-9 > top of Msdn Tech,Visual Basic,Visual Basic Language...
# 3

The type system is there to protect you from making unsafe operations. The difference between allowing:

1) Dim barinstance As Bar = DirectCast(Foo, Bar)

and

2) Barlist = DirectCast(FooList, List(Of Bar))

is that in case 1, if the runtime decides that the cast is incorrect, you will immediately get an invalid cast exception. If the cast is correct, then you *know* that barinstance can be used wherever a Foo can be used

If what you suggest would be allowed for 2, the cast would succeed, but you would have a Barlist that may or may not be safe to treat as a FooList. This is not really a type safe thing to do, and it *will* open you up for runtime exceptions later on in your code. Also, please note that even if the VB compiler would allow you to do this, the CLR would still throw! Type safety is an important feature in the Common Language Infrastructure (CLI)

Do you have a real world scenario where you need to do something like this? If so, would it be possible to explain a bit more - maybe we can find a way that avoids the need for this unsafe cast.

Best regards,
Johan Stenberg

JohanStenberg at 2007-9-9 > top of Msdn Tech,Visual Basic,Visual Basic Language...
# 4

Hello Johan,

the real world scenario is like in the first example. There are a lot of classes that inherit from the same baseclass. And there is a connection class for the connections of the instances.

There is one common base connection class and for each connection a class which inherits from that base connection class.

So I can say Class1_Con_To_Class2.Class1.PropertyOfClass1. It is type save, because Class1_Co_To_Class2 accepts only Class1 and Class2 Instances.

And so I have about thirty ClassX_Con_To_ClassY Classes. I thougt I could reduce this to one class with generics.

Regards, Markus

MarkusSch. at 2007-9-9 > top of Msdn Tech,Visual Basic,Visual Basic Language...
# 5
You could implement your own "cast" something like this:



Public Function ConvertTo(Of objConvFrom As clsBase, objConvTo As clsBase)() As clsBaseCon(Of objConvFrom, objConvTo)
Dim result As New clsBaseCon(Of objConvFrom, objConvTo)
result.BaseFrom =
DirectCast(CType(Me.BaseFrom, Object), objConvFrom
result.BaseTo =
DirectCast(CType(Me.BaseTo, Object), objConvTo)
Return result
End Function

in your clsBaseCon class. Instead of using the language's cast mechanism (which can't guarantee that your cast is safe), you can then use it as:



Con2 = Con1.ConvertTo(Of clsClass1, clsClass2)()

instead of your DirectCast.

Also, please note that in your first example, you have another type safety issue as well; you have assigned an instance of type clsClass2 to Con1.BaseTo, and you still expected the cast to an instance of clsBase(Of clsClass1, clsClass1) to work...

Type safety is your friend, not your enemy Smile

Best regards,
Johan Stenberg

JohanStenberg at 2007-9-9 > top of Msdn Tech,Visual Basic,Visual Basic Language...
# 6

Finally i did it like in the following code example.
Public Class Form2

Public MustInherit Class clsBase
Public Overridable ReadOnly Property Name() As String
Get
Return "This is Base."
End Get
End Property
End Class


Public Class clsClass1
Inherits clsBase

Public Overrides ReadOnly Property Name() As String
Get
Return "This is Class1."
End Get
End Property

Public RemarksClass1 As String = "Class1 Remarks..."
End Class


Public Class clsClass2
Inherits clsBase

Public Overrides ReadOnly Property Name() As String
Get
Return "This is Class2."
End Get
End Property

Public RemarksClass2 As String = "Class2 Remarks..."
End Class


Public Class clsClass3
Inherits clsBase

Public Overrides ReadOnly Property Name() As String
Get
Return "This is Class3."
End Get
End Property

Public RemarksClass3 As String = "Class3 Remarks..."
End Class


Public MustInherit Class clsBaseCon

End Class


Public MustInherit Class clsBaseConGeneric(Of ObjFrom As clsBase, _
ObjTo As clsBase)
Inherits clsBaseCon

Private mBaseFrom As ObjFrom
Private mBaseTo As ObjTo

Public Property BaseFrom() As ObjFrom
Get
Return mBaseFrom
End Get
Set(ByVal value As ObjFrom)
mBaseFrom = value
End Set
End Property

Public Property BaseTo() As ObjTo
Get
Return mBaseTo
End Get
Set(ByVal value As ObjTo)
mBaseTo = value
End Set
End Property

Public MustOverride Function InstanceCreate(ByVal pobjFrom As ObjFrom, _
ByVal pobjTo As ObjTo) _
As clsBaseConGeneric(Of ObjFrom, ObjTo)

Public Shared SharedInstance As clsBaseConGeneric(Of ObjFrom, ObjTo)

Shared Sub New()
SharedInstance = New clsPersistCon(Of ObjFrom, ObjTo)
End Sub
End Class


Public Class clsPersistCon(Of ObjFrom As clsBase, _
ObjTo As clsBase)
Inherits clsBaseConGeneric(Of ObjFrom, ObjTo)

Public Overrides Function InstanceCreate(ByVal pobjFrom As ObjFrom, _
ByVal pobjTo As ObjTo) _
As clsBaseConGeneric(Of ObjFrom, ObjTo)
Dim PersistCon As clsPersistCon(Of ObjFrom, ObjTo) = _
New clsPersistCon(Of ObjFrom, ObjTo)
PersistCon.BaseFrom = pobjFrom
PersistCon.BaseTo = pobjTo
Return PersistCon
End Function

End Class


Private Sub Form2_Click _
(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles Me.Click
Dim ObjClass1 As clsClass1 = New clsClass1
Dim ObjClass2 As clsClass2 = New clsClass2
Dim ObjClass3 As clsClass3 = New clsClass3

Dim conObjClass1_To_ObjClass2 As clsBaseConGeneric(Of clsClass1, clsClass2)
Dim conObjClass2_To_ObjClass3 As clsBaseConGeneric(Of clsClass1, clsClass3)

conObjClass1_To_ObjClass2 = clsBaseConGeneric(Of clsClass1, _
clsClass2).SharedInstance. _
InstanceCreate(ObjClass1, ObjClass2)
MsgBox(conObjClass1_To_ObjClass2.BaseTo.RemarksClass2)


conObjClass2_To_ObjClass3 = clsBaseConGeneric(Of clsClass1, _
clsClass3).SharedInstance. _
InstanceCreate(ObjClass1, ObjClass3)
MsgBox(conObjClass2_To_ObjClass3.BaseTo.RemarksClass3)

End Sub
End Class


MarkusSch. at 2007-9-9 > top of Msdn Tech,Visual Basic,Visual Basic Language...