Inheritance

twinBASIC provides several mechanisms for inheritance to support both simple and complete object-oriented programming patterns: Implements, Implements Via and Inherits.

Enhancements to Implements

Implements in twinBASIC has several enhancements:

Inherited Interfaces

Implements in twinBASIC is allowed on inherited interfaces – for instance, if you have Interface IFoo2 Extends IFoo, you then use Implements IFoo2 in a class, where in VBx this would not be allowed. You’ll need to provide methods for all inherited interfaces (besides IDispatch and IUnknown). The class will mark all interfaces as available– you don’t need a separate statement for IFoo, it will be passed through Set statements (and their underlying QueryInterface calls) automatically.

Multiple Implementations

If you have an interface that multiple others extend from, you can write multiple implementations, or specify one implementation for all. For example:

IOleWindow_GetWindow() As LongPtr _
    Implements IOleWindow.GetWindow, IShellBrowser.GetWindow, IShellView2.GetWindow

‘As Any’ Parameters in Interfaces

Implements is allowed on interfaces with ‘As Any’ parameters: In VBx, you’d get an error if you attempted to use any interface containing a member with an As Any argument. With twinBASIC, this is allowed if you substitute As LongPtr for As Any, for example:

Interface IFoo Extends IUnknown
    Sub Bar(ppv As Any)
End Interface

Class MyClass
    Implements IFoo

    Private Sub IFoo_Bar(ppv As LongPtr) Implements IFoo.Bar

    End Sub
End Class

Implements Via for Basic Inheritance

tB allows simple inheritance among classes. For example, if you have class cVehicle which implements IVehicle containing method Honk, you could create child classes like cCar or cTruck, which inherit the methods of the original, so you could call cCar.Honk without writing a separate implementation.

Code where class cCar implements IVehicle Via cVehicle, with the DEBUG CONSOLE showing HONK printed by both cars

You can see that the Honk method is only implemented by the parent class, then called from the child class when you click the CodeLens button to run the sub in place from the IDE.

Inherits for Complete OOP

This option supports full inheritance and OOP: Protected methods and variables accessible to derived classes (but not outside callers), Overridable and Overrides syntax, multiple inheritance, and explicit base class constructors.

Example: Animal Class Hierarchy

Important

Every class in this example is declared Private Class, because each one has a Sub New that takes arguments. A class that is not Private is exposed to COM, and COM creates objects without arguments. So a public class — base or derived — whose Sub New takes arguments fails to compile with TB5135, unless it also has a constructor that takes none. There are three fixes: declare the class Private, add the [COMCreatable(False)] attribute, or add a constructor without arguments, such as Class_Initialize. Only the third lets the class be declared As New; without it, As New fails with TB5121. See Parameterized Class Constructors.

Starting with a base class:

Private Class Animal
    Protected _name As String
    Protected _dob As Date  ' date of birth

    Public Event Spoke(ByVal sound As String)

    Public Sub New(name As String, dob As Date)
        _name = name
        _dob = dob
    End Sub

    Public Property Get Name() As String
        Name = _name
    End Property

    Public Property Get DOB() As Date
        DOB = _dob
    End Property

    ' Age in whole years based on DOB and today's date
    Public Function AgeYears() As Long
        Dim y As Long
        y = DateDiff("yyyy", _dob, Date)
        If DateSerial(Year(Date), Month(_dob), Day(_dob)) > Date Then y = y - 1
        AgeYears = y
    End Function

    Public Sub Speak()
        Dim s As String
        s = GetSound()
        RaiseEvent Spoke(s)
        Debug.Print _name & " says: " & s
    End Sub

    ' --- Overridable hook for derived classes ---
    Protected Overridable Function GetSound() As String
        GetSound = ""
    End Function
End Class

Others can inherit. Constructors are not inherited, so each derived class declares its own Sub New. Without one, a Cat cannot be created with arguments: New Cat("Misty", #20-Nov-2022#) fails with TB5030, Unexpected call arguments.

When the base class’s Sub New takes arguments, the derived class’s Sub New must call it — Dog calls Animal.New below, and GuardDog calls Dog.New. Nothing else calls it. If the call is left out — or the derived class has no Sub New and is created without arguments — the code still compiles and runs. The base constructor never runs, and the fields it sets keep their default values. No error or warning reports it. A base Sub New that takes no arguments is different: it runs automatically, before the derived class’s Sub New.

' ===== Derived: Dog =====
Private Class Dog
    Inherits Animal

    Protected _breed As String

    Public Sub New(name As String, dob As Date, breed As String)
        Animal.New(name, dob)               ' required: nothing else runs Animal's constructor
        _breed = breed
    End Sub

    Public Property Get Breed() As String
        Breed = _breed
    End Property

    ' Override:
    Protected Overridable Function GetSound() As String Overrides Animal.GetSound
        GetSound = "woof"
    End Function
End Class

' ===== Further derived: GuardDog (Dog → GuardDog) =====
Private Class GuardDog
    Inherits Dog

    Protected _onDuty As Boolean

    Public Sub New(name As String, dob As Date, breed As String)
        Dog.New(name, dob, breed)           ' required: runs Dog's constructor, which runs Animal's
        _onDuty = True
    End Sub

    Public Property Get OnDuty() As Boolean
        OnDuty = _onDuty
    End Property
    Public Property Let OnDuty(ByVal v As Boolean)
        _onDuty = v
    End Property

    ' Multi-level override (overriding Dog's override):
    Protected Function GetSound() As String Overrides Dog.GetSound
        If _onDuty Then
            GetSound = "WOOF!"
        Else
            GetSound = "woof"
        End If
    End Function
End Class

' ===== Derived: Cat =====
Private Class Cat
    Inherits Animal

    Public Sub New(name As String, dob As Date)
        Animal.New(name, dob)               ' required, although Cat adds no fields of its own
    End Sub

    Protected Function GetSound() As String Overrides Animal.GetSound
        GetSound = "meow"
    End Function
End Class

Dog’s GetSound is marked Overridable as well as Overrides, and that is what lets GuardDog override it again. Cat’s is not, so a class that inherits Cat cannot override GetSound: the compiler reports TB5068, procedure is not marked as Overridable.

Code that uses the classes goes in a Module. A procedure written at the top level of a .twin file, outside any Module or Class, does not compile: every line of it fails with TB5182, Syntax error. No handler for this symbol. This routine holds each animal in an Animal variable, and each call to Speak still uses the GetSound of the object’s own class:

Module AnimalsDemoMod
    Public Sub DemoAnimals()
        Dim pets(2) As Animal
        Set pets(0) = New Dog("Rex", #10-Feb-2019#, "Labrador")
        Set pets(1) = New GuardDog("Rover", #01-Jun-2018#, "German Shepherd")
        Set pets(2) = New Cat("Misty", #20-Nov-2022#)

        Dim i As Long
        For i = 0 To UBound(pets)
            pets(i).Speak
        Next i
    End Sub
End Module

Running DemoAnimals — for example from its CodeLens bar — prints this to the Debug Console:

Rex says: woof
Rover says: WOOF!
Misty says: meow

The classes come from Sample 23, which also has an AnimalWatcher class that handles the Spoke event, and a longer demonstration routine. To open it, choose File → New Project, then the Samples tab, and pick Sample 23. OOP Inheritance Example (Animals). The New Project page lists every sample.