How to extend derived classes by defining class(es) that exposes the instance as a property

Viewed 74

I have a class that I would like to extend by defining a new class that contains the first class as a public property, as well as additional added properties. However, the class that I'm extending has multiple derived types, which should be treated the same in the extension class.

Below is an example of what I am trying to do:

Public Class ClassA

End Class

Public Class ClassB
    Inherits ClassA
End Class

Public Class ClassC
    Inherits ClassA
End Class

Public Class BaseExtended
    Public Property Foo As ClassA
    Public Property ExtendedMetaData1 As Double
    Public Property ExtendedMetaData12 As Integer
End Class

Public Class DerivedExtendedB
    Inherits BaseExtended
    Public Property Foo As ClassB
End Class

Public Class DerivedExtendedC
    Inherits BaseExtended
    Public Property Foo As ClassC
End Class

The code that uses an instance of any of the 'extended' classes would then need use that instance appropriately depending on it's type. There would be many cases where the property 'Foo' needs to be accessed and modified outside of the class that it belongs to.

If I were to implement something like what I have shown above, that would require that I first cast it to the required type before accessing or modifying it. Ideally I would like to do that inside the 'DerivedExtended' class; The alternative, I think, would be to duplicate code to cast that property would [hundreds of times] in the client code.

Private Sub ClientUsesObject(bar As BaseExtended)
    ' Perform a task that is agnostic Foo type
    ' Would not require that Foo be cast to any specific type
    If bar.GetType() Is GetType(DerivedExtendedB) Then
        Dim barCast As DerivedExtendedB = DirectCast(bar, DerivedExtendedB)
        ' Perform task that requires Foo to be of type ClassB
    ElseIf bar.GetType() Is GetType(DerivedExtendedC) Then
        Dim barCast As DerivedExtendedC = DirectCast(bar, DerivedExtendedC)
        ' Perform task that requires Foo to be of type ClassC
    End If
End Sub

What I'm looking for is advice outlining or describing a design pattern that can handle this situation. I've searched for quite a while, and have not been able to find any examples that solve this problem.

I realize that this may be somewhat of an "XY" problem. I'm working with existing code that simply assumes all instances are of the same derived type (when in fact some instances are of the other derived type). As such, the existing code does not work. To me what I've tried to outline above seems like the most straightforward path, but I'm open to alternative if this is just the wrong approach.

1 Answers

This pattern of type covariance in derived classes is the canonical reason for what is called in C++ the "Curiously Recurring Template Pattern" and has been called in .NET the "Curiously Recurring Generic Pattern." I believe it's also sometimes referred to as "F-Bounded Polymorphism" (not a computer scientist, so I might have the reference wrong).

You can write a base class like this:

Public Class Base(Of TDerived As Base)
    Public Overridable Property foo As TDerived
End Class

And then use it like this:

Public Class MyDerived
    Inherits Base(Of MyDerived)
End Class

Then, the derived class has a property foo whose type is MyDerived. No casting required by clients.

However, this has some limitations. It works best when you don't need to switch back and forth between derived and base. There is no one Base, so you can't declare instances of it. If you want to be able to declare something as Base, then you end up needing to fall back on a non-generic base class. This will still work well for certain usage patterns where you don't need to convert from base to derived, but otherwise you run right back into the casting problems you are trying to avoid.

Eric Lippert has written a bit about this pattern. He's always interesting to read, so I'd recommend looking up his commentary.

Another alternative to consider, if the generic approach doesn't work for you, is code generation. You can use T4 templates to process a compact description of what your code should be, and generate the code files from them. A long list of casts is less tedious if you only write the machinery to generate it, you don't write them all out explicitly.

Related