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
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
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
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
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
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 
Best regards,
Johan Stenberg
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