Casting Generic Interface to base type - vb.net

I'm struggling to get this hierarchy working with generics. The problem is that Items is generic, specified at the inheritance level, therefore I cannot cast back to BaseItem, i.e. casting from SpecialItem(Of ExtraSpecialItem) to IItemHost(Of BaseItem) when SpecialItem inherits BaseGroup, as BaseGroup implements IItemHost.
What am I doing wrong here?
Public MustInherit Class BaseItem
Public Property ItemNameOrSomething As String
End Class
Public Interface IItemHost(Of TItemType As {BaseItem})
Property Items As BindingList(Of TItemType) '-- No Out parameter allowed :(
End Interface
Public Class BaseGroup(Of TGroup AS {BaseItem})
Inherits BaseItem
Implements IItemHost(Of TGroup)
'-- This is the key property, all BaseGroup implimentors need an Items property of their specific type
Public Property Items As New BindingList(Of TGroup)() Implements IItemHost(Of TGroup).Items
End Class
Public Class SpecialItem
Inherits BaseGroup(Of ExtraSpecialItem)
End Class
Public Class ExtraSpecialItem
Inherits BaseGroup(Of LeafItem)
End Class
Public Class LeafItem
Inherits BaseItem
End Class
For the most part, this all actually works. What I cannot do is:
Dim root = New SpecialItem()
root.ItemNameOrSomething = "Testing 1"
root.Items.Add(New ExtraSpecialItem() With {.ItemNameOrSomething = "Testing 2"})
'-- This specifically, no casting options available.
Dim item = CType(root, IItemHost(Of BaseItem))
Dim subItems = item.Items
Dim testing2Text = subItems.First().ItemNameOrSomething '-- = "Testing 2"

Ok this hasn't exactly solved the problem, but it's a solution I'm willing to settle with for now.
If I change my BaseItem to have a "BaseItems" collection, my inherited BaseGroup classes can have a default Items IEnumerable. If I need to write back to this collection, I can simply use BaseItems. For standard looping through the items, I can use Items and this will give me the proper casting.
Public MustInherit Class BaseItem
Public Property ItemNameOrSomething As String
Public Property BaseItems As New BindingList(Of BaseItem)()
End Class
Public Class BaseGroup(Of TGroup As {BaseItem})
Inherits BaseItem
Public ReadOnly Property Items As IEnumerable(Of TGroup)
Get
Return BaseItems.Cast(Of TGroup)()
End Get
End Property
End Class

Related

VB.NET WinForms implementing an interface

VB.NET
I want some WinForms to implement an interface, and be able to pass these to a procedure which can 'see' the implemented properties as well as the 'standard methods' of a Form. This is what I have so far...
Public Interface IMyInterface
Property MyProperty As String
End Interface
Public Class MyForm
Implements IMyInterface
Private _MyProperty As String
Public Property MyProperty() As String Implements IMyInterface.MyProperty
Get
Return _MyProperty
End Get
Set(ByVal value As String)
_MyProperty = value
End Set
End Property
End Class
then, elsewhere I have my method as follows...
Public Sub DoSomething(MyForm As IMyInterface)
MyForm.MyProperty = "x"
MyForm.ShowDialog()
End Sub
The obvious problem is that the compiler doesn't know what .ShowDialog is, and if I pass my form in as 'MyForm As Form' it doesn't know what 'MyProperty' is. I understand the reasons for this, but not how to solve this problem. Is a simple casting to Form the correct way to address this?
Many thanks.
You need to inherit System.Windows.Forms.Form to gain all regular form functionality and then Implement IMyInterface.
Public Class MyForm
Inherits System.Windows.Forms.Form
Implements IMyInterface
Passing MyForm As IMyInterface into the DoSomething() method is fine, however to use the regular form methods you'll need to cast it. Alternatively you could pass in the Form and then cast to IMyInterface, your choice.
Public Sub DoSomething(MyIForm As IMyInterface)
MyIForm.MyProperty = "x"
Dim MyForm As Form = TryCast(MyIForm, Form)
MyForm.ShowDialog()
Another aproach can be using your own base class
Public Class MyBaseForm
Inherits System.Windows.Forms.Form
Public Property MyProperty As String
End Class
Then all your forms can inherit that base form
Public Class MyForm
Inherits MyBaseForm
End Class
And you can use property and standard method of System.Windows.Forms.Form without casting
Public Sub DoSomething(someform As MyForm)
someform.MyProperty = "Some value"
someform.ShowDialog()
End Sub

How do I get a superclass to reference a property in a subclass when the subclass invokes the superclass' method?

The superclass:
Public MustInherit Class Product
Friend _shortName as String = Nothing
Public ReadOnly Property Name as String
Get
return _shortName
End Get
End Property
End Class
The Sub class
Public Class MyProduct : Inherits Product
Friend Shadows _shortName as String = "MyProd"
End Class
So, in the immediate console when I'm debugging, I do:
Dim product as new MyProduct
product.Name ' => Nothing
product.Name should be "MyProd" - but it isn't. How do I set this up correctly, so that the the property defined in the superclass accesses the field defined in the subclass?
There is no way for the base class to access the shadowed version of the field. Shadows should be avoided unless it is absolutely necessary. For something like this, you should just change the value of the base field from the derived class. There is no need to shadow it:
Public Class MyProduct : Inherits Product
Public Sub New()
_shortName = "MyProd"
End Sub
End Class
It's worth mentioning that, unless you really need it to be scoped as Friend, the _shortName field in the base class should be scoped as Protected.
Although, in this example, it looks like you probably want all derived classes to provide the name. In that case, there are two ways to accomplish that. You could require the name as a parameter in the base class' constructor:
Public MustInherit Class Product
Public Sub New(shortName As String)
_shortName = shortName
End Sub
Friend _shortName As String = Nothing
Public ReadOnly Property Name As String
Get
Return _shortName
End Get
End Property
End Class
Public Class MyProduct : Inherits Product
Public Sub New()
MyBase.New("MyProd")
End Sub
End Class
In this case, the _shortName doesn't even need to be Friend or Protected. It should ideally be scoped as Private.
Or, you could simply declare the property as MustOverride:
Public MustInherit Class Product
Public MustOverride ReadOnly Property Name As String
End Class
Public Class MyProduct : Inherits Product
Public Overrides ReadOnly Property Name As String
Get
Return "MyProd"
End Get
End Property
End Class

VB.NET Abstract Property

I have an abstract "GridBase" class with two types of derived classes "DetailGrid" and "HeaderGrid".
Respectively, one is comprised of "DetailRow" objects and the other "HeaderRow" objects. Both of those inherit from a "RowBase" abstract class.
What I am trying to do is the following:
Public MustInherit Class GridBase
Private pRows As List(Of RowBase)
Public ReadOnly Property Rows As List(Of RowBase)
Get
Return pRows
End Get
End Property
End Class
Public Class DetailGrid
Inherits GridBase
End Class
In this scenario, I want DetailGrid.Rows to return a list of DetailRow. I want HeaderRow.Rows to return a list of HeaderRow. Am I on the right track with this or should the Rows property not be included in the GridBase class?
If you want a stronger typing guarantee, then you probably want:
Public MustInherit Class GridBase(Of T as RowBase)
Private pRows As List(Of T)
Public ReadOnly Property Rows As List(Of T)
Get
Return pRows
End Get
End Property
End Class
Public Class DetailGrid
Inherits GridBase(Of DetailRow)
End Class

How can I control the element names of serialized subclasses?

Let's say I have the following class structure (simplified from my real-world problem):
Public Class PC_People_Container
Private _people_list As New List(Of PL_Person)
Public Sub New()
End Sub
Public Sub Add(ByVal item As PL_Person)
_people_list.Add(item)
End Sub
Public Property PeopleList As List(Of PL_Person)
Get
Return _people_list
End Get
Set(ByVal value As List(Of PL_Person))
_people_list = value
End Set
End Property
End Class
Public Class PL_Person
Private _Name As String
Public Property Name As String
Get
Return _Name
End Get
Set(ByVal value As String)
_Name = value
End Set
End Property
Private _Contacts As ContactObject
Public Property Contacts As ContactObject
Get
Return _Contacts
End Get
Set(ByVal value As ContactObject)
_Contacts = value
End Set
End Property
Public Sub New()
End Sub
End Class
Public Class ContactObject
Public Property PhoneNumber As String
Public Property EmailAddress As String
Public Sub New()
End Sub
End Class
If I were to serialize this, I'd get the default assigned node names in my XML. That means my root is named PC_People_Container and each person in the list is marked up as PL_Person. I know I can change the root node using <XmlRoot(ElementName:="PeopleContainer")>. The trouble is doing that for the subclasses. I can't use the <XmlRoot> tag on PL_Person class because there can't be two root elements, and IntelliSense throws a fit when I try to use the <XmlElement> tag on a class like I would on a property. Is it even possible to control what those subclasses are named when they're serialized as child nodes?
PL_Person and ContactObject are not subclasses as you call them, they are merely property types.
This makes your question confusing because it suggests you may have a problem with inheritance (subclasses are classes that inherit from some base class) when in fact you just want your property elements to be named differently.
You should decorate your properties (not classes) with <XmlElement> to specify custom name:
<XmlElement("Persons", GetType(PL_Person))>
Public Property PeopleList As List(Of PL_Person)
As an afterthought, I would definitely not recommend calling your classes using such an awkward convention. In .NET, you should not use any prefixes or underscores in class names. Just call it Person.

Can I inherit from a generic class without specifying a type?

I have the following sample code in a VB.NET console application. It compiles and works, but feels like a hack. Is there a way to define EmptyChild so that it inherits from Intermediate(Of T As Class) without using the dummy EmptyClass?
Module Module1
Sub Main()
Dim Child1 = New RealChild()
Child1.Content = New RealClass()
Dim Child2 = New EmptyChild()
Console.WriteLine("RealChild says: " & Child1.Test)
Console.WriteLine("EmptyChild says: " & Child2.Test)
Console.ReadLine()
End Sub
Public Class EmptyClass
End Class
Public Class RealClass
Public Overrides Function ToString() As String
Return "This is the RealClass"
End Function
End Class
Public MustInherit Class Base(Of T As Class)
Private _content As T = Nothing
Public Property Content() As T
Get
Return _content
End Get
Set(ByVal value As T)
_content = value
End Set
End Property
Public Overridable Function Test() As String
If Me._content IsNot Nothing Then
Return Me._content.ToString
Else
Return "Content not initialized."
End If
End Function
End Class
Public MustInherit Class Intermediate(Of T As Class)
Inherits Base(Of T)
'some methods/properties here needed by Child classes
End Class
Public Class RealChild
Inherits Intermediate(Of RealClass)
'This class needs all functionality from Intermediate.
End Class
Public Class EmptyChild
Inherits Intermediate(Of EmptyClass)
'This class needs some functionality from Intermediate,
' but not the Content as T property.
Public Overrides Function Test() As String
Return "We don't care about Content property or Type T here."
End Function
End Class
End Module
The other way to do this would be to move the generic code out of the Base class and then create 2 Intermediate classes like this:
Public MustInherit Class Intermediate
Inherits Base
'some methods/properties here needed by Child classes
End Class
Public MustInherit Class Intermediate(Of T As Class)
Inherits Intermediate
'implement generic Content property here
End Class
Then RealChild would inherit from the generic Intermediate and EmptyChild would inherit from the non-generic Intermediate. My problem with that solution is that the Base class is in a separate assembly and I need to keep the code that handles the generic type in that assembly. And there is functionality in the Intermediate class that does not belong in the assembly with the Base class.
Yes, you need to specify a type parameter when you inherit, or your EmptyChild must be generic as well. But, you don't have to dummy up a EmptyClass - just use Object as your type parameter:
Public Class EmptyClass
Inherits Intermediate(Of Object)
End Class