Inheritance and Polymorphism

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Visual Basic 17.13 on .NET 10 (SDK 10.0.402) and was checked on 2026-10-19. Every example is a complete console program that starts with Option Strict On. The examples were built (with warnings treated as errors, except the ones that are meant to show compiler messages, which say so) and run in the .NET 10 SDK container (Linux), and the output shown is the real output. The project root namespace is Shelf, which is why runtime messages say Shelf.Book. The project-level imports that the SDK adds (System, System.Collections.Generic, System.Linq and others) are why Console and List(Of T) need no Imports statement.

Inheritance lets a class reuse and specialise the members of another class: a Book is a CatalogItem, so code that works with catalogue items works with books, and the same call on the same variable behaves differently depending on the object it holds. That second property is polymorphism, and it is the reason to inherit at all; sharing code is better done by composition. The running example is the shelf library catalogue: an abstract CatalogItem with Book and Magazine derived from it, held by a Catalog and lent through a Loan.

Visual Basic has had implementation inheritance since Visual Basic .NET 1.0 (2002). Visual Basic 6 and VBA had only interface inheritance (Implements), so a programmer who comes from them must learn everything on this page; a programmer who comes from C# knows the ideas but meets different keywords and a handful of rules that differ in ways that matter (the order in which field initializers run, hiding by name, no covariant return types, no type patterns).

The shelf hierarchy

classDiagram class CatalogItem { <<abstract>> +Id Integer +Title String +Year Integer #New(title, year) +Kind() String* +Describe() String } class Book { +Isbn Isbn +Authors List~Author~ +Kind() String +Describe() String } class Magazine { +IssueNumber Integer +Kind() String } class Author { +Name String +BornOn Date } class Loan { +Item CatalogItem +Borrower String +DueOn Date } CatalogItem <|-- Book CatalogItem <|-- Magazine Book "1" o-- "*" Author : Authors Loan --> CatalogItem : Item

CatalogItem is abstract (MustInherit): there is no such thing as a catalogue item that is neither a book nor a magazine, so the class cannot be instantiated, and Kind is a MustOverride member that every concrete class must supply (the asterisk marks it as abstract in the diagram). Describe is Overridable, with a default implementation that Book extends. Loan depends on the abstraction, so it lends books and magazines alike.

Declaring a derived class

[attributes] [access] [Shadows] [MustInherit | NotInheritable] Class Name
    Inherits BaseClass
    ...
End Class
  • Inherits must be the first statement in the class body, before any declaration (only comments and blank lines may precede it). A class has at most one base class (BC30121 is reported for a second name). The statement may also follow the class header after a colon: Public Class LoudFormatter : Inherits Formatter.

  • The base must be a class. Inherits IDisposable is BC30258 ("Classes can inherit only from other classes"): to implement an interface use Implements (see Interfaces). A class cannot inherit from a class declared NotInheritable (BC30299), which includes String and every structure.

  • A class that has no Inherits statement inherits from System.Object. Every class, structure, enumeration, delegate and array is therefore also an Object.

  • Structures cannot use Inherits (BC30628): they derive from System.ValueType implicitly, and enumerations from System.Enum. Modules are sealed classes that cannot be inherited either (see Modules and Shared Members).

  • A derived class inherits every member of its base class except constructors and finalizers, but it can only use the ones its access level allows (see Access modifiers and inheritance).

  • Classes are inheritable by default in Visual Basic, as in C#. Add NotInheritable to forbid it. The framework design guidelines recommend sealing a class that was not designed to be extended.

  • A class nested in a base class is inherited as a member and can be shadowed by a nested class of the same name in the derived class (see Shadows versus Overloads versus Overrides).

Everything is an Object

The BaseType property shows the chain of base types. The following program prints it for several kinds of type, which confirms where Object, ValueType, Enum, Array, Delegate and the exception hierarchy sit, and that interfaces have no base type.

Option Strict On

Public Enum LoanState
    Active = 1
End Enum

Public Class Plain
End Class

Public Structure Point2
    Public X As Integer
End Structure

Module Program
    Sub Chain(t As Type)
        Dim parts As New List(Of String)
        Dim current As Type = t
        While current IsNot Nothing
            parts.Add(current.Name)
            current = current.BaseType
        End While
        Console.WriteLine(String.Join(" -> ", parts))
    End Sub

    Sub Main()
        Chain(GetType(Plain))
        Chain(GetType(Point2))
        Chain(GetType(LoanState))
        Chain(GetType(Integer))
        Chain(GetType(Integer()))
        Chain(GetType(Action))
        Chain(GetType(ArgumentNullException))
        Chain(GetType(List(Of Plain)))
        Chain(GetType(IDisposable))
        Chain(GetType(Object))
        Console.WriteLine()

        Dim p As New Plain()
        Console.WriteLine(p.ToString())
        Console.WriteLine(p.Equals(New Plain()) & " " & p.Equals(p) & " " & Object.ReferenceEquals(p, p))
        Console.WriteLine(p.GetHashCode() = p.GetHashCode())
        Console.WriteLine(CObj(5).ToString() & " " & CObj(5).GetType().FullName)
    End Sub
End Module
Plain -> Object
Point2 -> ValueType -> Object
LoanState -> Enum -> ValueType -> Object
Int32 -> ValueType -> Object
Int32[] -> Array -> Object
Action -> MulticastDelegate -> Delegate -> Object
ArgumentNullException -> ArgumentException -> SystemException -> Exception -> Object
List`1 -> Object
IDisposable
Object

Shelf.Plain
False True True
True
5 System.Int32

Source: Type.BaseType

Four facts follow from the output.

  1. Object.ToString returns the full type name (Shelf.Plain) unless you override it, which is why overriding ToString on every domain class is such a common first exercise.

  2. Object.Equals compares references for classes (False for two different instances, True for the same one). Value equality has to be written (see Operator Overloading and Equality).

  3. A Module is compiled to a sealed class; a structure is a ValueType; an enumeration is an Enum; an array type derives from System.Array, and a delegate type from MulticastDelegate.

  4. An interface type has no base type (BaseType is Nothing), although every object that implements it is an Object.

A complete hierarchy

The following program declares CatalogItem, Book and Magazine as in the diagram (the ISBN is simplified to a string so that the program stays self-contained) and uses them polymorphically through a List(Of CatalogItem).

Option Strict On

Public MustInherit Class CatalogItem
    Public Property Title As String
    Public Property Year As Integer

    Protected Sub New(title As String, year As Integer)
        Me.Title = title
        Me.Year = year
    End Sub

    Public MustOverride ReadOnly Property Kind As String

    Public Overridable Function Describe() As String
        Return $"{Kind}: {Title} ({Year})"
    End Function
End Class

Public Class Book
    Inherits CatalogItem

    Public ReadOnly Property Authors As New List(Of String)

    Public Sub New(title As String, year As Integer, ParamArray authors As String())
        MyBase.New(title, year)
        Me.Authors.AddRange(authors)
    End Sub

    Public Overrides ReadOnly Property Kind As String
        Get
            Return "Book"
        End Get
    End Property

    Public Overrides Function Describe() As String
        Return MyBase.Describe() & " by " & String.Join(", ", Authors)
    End Function
End Class

Public Class Magazine
    Inherits CatalogItem

    Public Property IssueNumber As Integer

    Public Sub New(title As String, year As Integer, issueNumber As Integer)
        MyBase.New(title, year)
        Me.IssueNumber = issueNumber
    End Sub

    Public Overrides ReadOnly Property Kind As String = "Magazine"
End Class

Module Program
    Sub Main()
        ' Dim bad = New CatalogItem("x", 2020)   ' BC30569: New cannot be used on a MustInherit class
        Dim items As New List(Of CatalogItem) From {
            New Book("The Pragmatic Programmer", 1999, "Hunt", "Thomas"),
            New Magazine("Byte", 1984, 12),
            New Book("Domain-Driven Design", 2003, "Evans")
        }

        For Each item In items
            Console.WriteLine(item.Describe())
        Next

        Dim first As CatalogItem = items(0)
        Console.WriteLine(first.GetType().Name & " / " & TypeName(first))
        Console.WriteLine(first.Kind)
    End Sub
End Module
Book: The Pragmatic Programmer (1999) by Hunt, Thomas
Magazine: Byte (1984)
Book: Domain-Driven Design (2003) by Evans
Book / Book
Book

What the example shows.

  • Protected Sub New in an abstract class: only derived classes can call it (through MyBase.New), so the constructor does not appear in the public surface of the class. When a MustInherit class declares no constructor, the compiler generates a Protected one; a concrete class gets a Public one (verified by reflection in Constructors in a hierarchy).

  • MustOverride ReadOnly Property Kind has no body and no End Property. Book overrides it with an explicit Get block, Magazine with a read-only auto-property that has an initializer (read-only auto-properties with initializers arrived in Visual Basic 14; overriding them is allowed).

  • Describe is Overridable; Book.Describe calls MyBase.Describe() to extend the base behaviour instead of replacing it, and Magazine inherits the default.

  • Dim first As CatalogItem = items(0) holds a Book. first.GetType() and TypeName(first) report the run-time class (Book), while the compiler only knows the static type (CatalogItem).

  • The commented line shows the error BC30569 for New CatalogItem(...): an abstract class is never instantiated.

Abstract and sealed classes

MustInherit and MustOverride define a contract that the compiler enforces; NotInheritable and NotOverridable close a hierarchy at the places where the design says it is finished.

Modifier Meaning C# equivalent

MustInherit (class)

Cannot be instantiated; may contain implemented members, fields and constructors, and MustOverride members

abstract class

MustOverride (member)

No body, implicitly Overridable; derived concrete classes must override it; the class must be MustInherit (BC31411)

abstract member

NotInheritable (class)

Cannot be the base of another class (BC30299)

sealed class

NotOverridable (member)

Only on an Overrides member: further derived classes cannot override it again (BC31088 without Overrides)

sealed override

  • A concrete class that inherits a MustOverride member without overriding it is rejected with BC30610 (the message lists the missing members). Declaring the class MustInherit too postpones the obligation to its own derived classes.

  • MyBase.Kind or MyClass.Id() on a MustOverride member is BC30399 or BC30614: there is no implementation to call.

  • NotOverridable in a NotInheritable class is BC30607, because nothing can override anyway. Seal the class, or seal the individual member, not both.

  • An abstract class can implement an interface without implementing its members, by redeclaring them MustOverride (see Interfaces).

  • Visual Basic has no static class equivalent other than a Module; a NotInheritable class with only Shared members and a Private Sub New is the other way to say it.

The following file collects the declaration errors in one place. It is meant to fail, so it was built with TreatWarningsAsErrors off, and it contains one mistake per commented line. The compiler reports all of them in one run, but only because they are declaration-level errors; errors inside method bodies appear after these are fixed.

Option Strict On

Public MustInherit Class CatalogItem
    Public MustOverride ReadOnly Property Kind As String
    Public Overridable Function Describe() As String
        Return "item"
    End Function
    Public Function Fixed() As String
        Return "fixed"
    End Function
    Public Overridable Function Self() As CatalogItem
        Return Me
    End Function
    Public Overridable ReadOnly Property Count As Integer
        Get
            Return 0
        End Get
    End Property
End Class

Public Class Unfinished                     ' E1: the MustOverride member Kind is not implemented
    Inherits CatalogItem
End Class

Public Class Book
    Inherits CatalogItem
    Public Overrides ReadOnly Property Kind As String = "Book"
    Public Overrides Function Fixed() As String                     ' E2: Fixed is not Overridable
        Return "x"
    End Function
    Protected Overrides Function Describe() As String               ' E3: different access level
        Return "x"
    End Function
    Public Overrides Function Self() As Book                        ' E4: no covariant return types
        Return Me
    End Function
    Public Overrides Property Count As Integer                      ' E5: cannot add a setter
        Get
            Return 1
        End Get
        Set(value As Integer)
        End Set
    End Property
End Class

Public NotInheritable Class Sealed
    Inherits CatalogItem
    Public Overrides ReadOnly Property Kind As String = "Sealed"
End Class

Public Class ExtendsSealed                  ' E6: the base class is NotInheritable
    Inherits Sealed
End Class

Public Class FromString                     ' E7: so is String
    Inherits String
End Class

Public Class TwoBases                       ' E8: only one Inherits clause
    Inherits Book, Object
End Class

Public Class FromInterface                  ' E9: classes inherit only from classes
    Inherits IDisposable
End Class

Public Class Forgetful                      ' E10: MustOverride in a class that is not MustInherit
    Public MustOverride Function Go() As Integer
End Class

Public Structure Pair                       ' E11: structures cannot inherit
    Inherits CatalogItem
End Structure

Public Class Modifiers
    Public Shared Overridable Function Make() As Integer    ' E12: Shared cannot be Overridable
        Return 1
    End Function
    Private Overridable Function Hidden() As Integer        ' E13: Private cannot be Overridable
        Return 2
    End Function
End Class

Public Class Sealing
    Public NotOverridable Function Lock() As Integer        ' E14: NotOverridable needs Overrides
        Return 3
    End Function
End Class

Public Class OpenBase
    Inherits CatalogItem
    Public Overrides ReadOnly Property Kind As String = "Open"
    Public Overrides Function Describe() As String
        Return "open"
    End Function
End Class

Public NotInheritable Class Closed
    Inherits OpenBase
    Public NotOverridable Overrides Function Describe() As String   ' E15: pointless in a NotInheritable class
        Return "closed"
    End Function
End Class

Module Program
    Sub Main()
    End Sub
End Module

The compiler printed these messages (paths shortened; the line and column are those of the listing above):

Program.vb(100,12): error BC30607: 'NotInheritable' classes cannot have members declared 'NotOverridable'.
Program.vb(21,14): error BC30610: Class 'Unfinished' must either be declared 'MustInherit' or override the following inherited 'MustOverride' member(s):
Program.vb(28,31): error BC31086: 'Public Overrides Function Fixed() As String' cannot override 'Public Function Fixed() As String' because it is not declared 'Overridable'.
Program.vb(31,34): error BC30266: 'Protected Overrides Function Describe() As String' cannot override 'Public Overridable Function Describe() As String' because they have different access levels.
Program.vb(34,31): error BC30437: 'Public Overrides Function Self() As Book' cannot override 'Public Overridable Function Self() As CatalogItem' because they differ by their return types.
Program.vb(37,31): error BC30362: 'Public Overrides Property Count As Integer' cannot override 'Public Overridable ReadOnly Property Count As Integer' because they differ by 'ReadOnly' or 'WriteOnly'.
Program.vb(52,14): error BC30299: 'ExtendsSealed' cannot inherit from class 'Sealed' because 'Sealed' is declared 'NotInheritable'.
Program.vb(56,14): error BC30299: 'FromString' cannot inherit from class 'String' because 'String' is declared 'NotInheritable'.
Program.vb(60,5): error BC30121: 'Inherits' can appear only once within a 'Class' statement and can only specify one class.
Program.vb(64,14): error BC30258: Classes can inherit only from other classes.
Program.vb(67,14): error BC31411: 'Forgetful' must be declared 'MustInherit' because it contains methods declared 'MustOverride'.
Program.vb(72,5): error BC30628: Structures cannot have 'Inherits' statements.
Program.vb(76,19): error BC30501: 'Shared' cannot be combined with 'Overridable' on a method declaration.
Program.vb(79,13): error BC31408: 'Private' and 'Overridable' cannot be combined.
Program.vb(85,36): error BC31088: 'NotOverridable' cannot be specified for methods that do not override another method.

The message for BC30610 ends with a colon because the build output of the SDK omits the list of missing members that Visual Studio shows below it. The messages cross-reference as follows: E1 BC30610, E2 BC31086, E3 BC30266, E4 BC30437, E5 BC30362, E6 and E7 BC30299, E8 BC30121, E9 BC30258, E10 BC31411, E11 BC30628, E12 BC30501, E13 BC31408, E14 BC31088 and E15 BC30607.

Overriding members

Visual Basic members are not virtual by default, as in C#. A base class opts in with Overridable (or MustOverride), and a derived class takes part with Overrides. Both sides are explicit, so reading the keyword on a method tells you whether the call is resolved at run time.

Keyword Effect C# equivalent

Overridable

The member can be overridden; calls through a base-typed reference reach the most derived override

virtual

Overrides

Replaces an inherited Overridable, MustOverride or already-overriding member; is itself overridable again unless NotOverridable is added

override

NotOverridable

Seals an overriding member (only valid together with Overrides)

sealed override

MustOverride

Declares the member without a body in a MustInherit class

abstract

Shadows

Hides an inherited member of any kind by name; no run-time dispatch

new

Overloads

Adds a member with the same name and a different signature next to the inherited ones

(implicit in C#)

MyBase

Non-virtual call of the base class implementation

base

MyClass

Non-virtual call of the member of the current class, ignoring overrides

no equivalent

Me

The current object

this

An override has to repeat the signature of the member it replaces. The compiler checks each part with a separate error, which makes the messages a good checklist; every code in the table is produced by one of the listings of this page.

What must match Error Example

Return type, exactly (no covariant returns)

BC30437

Overrides Function Self() As Book over Function Self() As CatalogItem

Access level

BC30266

Protected Overrides over Public Overridable

ReadOnly / WriteOnly of a property

BC30362

adding a Set to an inherited read-only property

Default values of Optional parameters

BC30307

see below

Base member must be overridable

BC31086

Overrides of a plain method

Two points deserve a closer look.

No covariant return types. C# 9 lets an override return a more derived type; Visual Basic 17.13 does not (BC30437 in the listing above). The usual workaround is a second, non-virtual member with the more specific type.

Optional parameters and overrides. An override must declare the same optional parameters with the same default values, otherwise the call would depend on the static type of the reference. The compiler refuses the mismatch with BC30307:

Option Strict On

Public Class Base
    Public Overridable Function Format(value As Integer, Optional prefix As String = "base") As String
        Return prefix & ":" & value
    End Function
End Class

Public Class Derived
    Inherits Base

    Public Overrides Function Format(value As Integer, Optional prefix As String = "derived") As String
        Return "[" & MyBase.Format(value, prefix) & "]"
    End Function
End Class

Module Program
    Sub Main()
        Dim d As New Derived()
        Dim b As Base = d
        Console.WriteLine(d.Format(1))
        Console.WriteLine(b.Format(1))
    End Sub
End Module
Program.vb(12,31): error BC30307: 'Public Overrides Function Format(value As Integer, [prefix As String = "derived"]) As String' cannot override 'Public Overridable Function Format(value As Integer, [prefix As String = "base"]) As String' because they differ by the default values of optional parameters.

Source: Overrides

The template method pattern

A public method that fixes an algorithm and calls Protected steps that derived classes may replace is the safest way to offer extension: the base class keeps control of the order of the steps, and the extension points are small. Protected MustOverride forces a subclass to supply the essential step, Protected Overridable offers an optional one, and NotOverridable Overrides seals the diagnostic text, which is the same in every subclass.

Option Strict On

Public MustInherit Class LoanPolicy
    ' Template method: the algorithm is fixed, the steps are overridable.
    Public Function DueDate(borrowed As Date) As Date
        Dim days = BaseDays()
        Dim due = borrowed.AddDays(days)
        Return AdjustForHolidays(due)
    End Function

    Protected MustOverride Function BaseDays() As Integer

    Protected Overridable Function AdjustForHolidays(due As Date) As Date
        Return due                                     ' default: no adjustment
    End Function

    Public NotOverridable Overrides Function ToString() As String
        Return Me.GetType().Name & "(" & BaseDays() & " days)"
    End Function
End Class

Public Class StandardPolicy
    Inherits LoanPolicy

    Protected Overrides Function BaseDays() As Integer
        Return 14
    End Function
End Class

Public Class WeekdayPolicy
    Inherits StandardPolicy

    Protected Overrides Function AdjustForHolidays(due As Date) As Date
        Select Case due.DayOfWeek
            Case DayOfWeek.Saturday : Return due.AddDays(2)
            Case DayOfWeek.Sunday : Return due.AddDays(1)
            Case Else : Return due
        End Select
    End Function
End Class

Public NotInheritable Class ShortPolicy
    Inherits WeekdayPolicy

    Protected Overrides Function BaseDays() As Integer
        Return MyBase.BaseDays() \ 2
    End Function
End Class

Module Program
    Sub Main()
        Dim policies As LoanPolicy() = {New StandardPolicy(), New WeekdayPolicy(), New ShortPolicy()}
        Dim borrowed As New Date(2026, 10, 17)     ' a Saturday
        For Each policy In policies
            Dim due = policy.DueDate(borrowed)
            Console.WriteLine(policy.ToString() & " -> " & due.ToString("yyyy-MM-dd ddd", Globalization.CultureInfo.InvariantCulture))
        Next
    End Sub
End Module
StandardPolicy(14 days) -> 2026-10-31 Sat
WeekdayPolicy(14 days) -> 2026-11-02 Mon
ShortPolicy(7 days) -> 2026-10-26 Mon

Source: NotOverridable

  • WeekdayPolicy overrides only the optional step and inherits the 14 days; ShortPolicy overrides the essential step and calls MyBase.BaseDays() (the 14 days of StandardPolicy) to halve it. \ is the integer division operator. ShortPolicy inherits the weekday adjustment of its base class: 2026-10-24 is a Saturday, so the due date moves to Monday.

  • DueDate is not overridable, so no subclass can change the algorithm. ToString is sealed with NotOverridable Overrides, an override of a member that was declared in Object, so no subclass can change the text.

  • The 2026-10-17 borrow date is a Saturday. For StandardPolicy the due date, 14 days later, is also a Saturday.

How the compiler emits the modifiers

Reflection exposes what the keywords turn into in the assembly. The program prints the relevant flags for a few members of a small hierarchy.

Option Strict On
Imports System.Reflection

Public MustInherit Class CatalogItem
    Public MustOverride ReadOnly Property Kind As String
    Public Overridable Function Describe() As String
        Return Kind
    End Function
    Public Function Plain() As String
        Return "plain"
    End Function
    Public Shared Function Create() As String
        Return "shared"
    End Function
End Class

Public Class Book
    Inherits CatalogItem
    Public Overrides ReadOnly Property Kind As String = "Book"
    Public Overrides Function Describe() As String
        Return "Book " & MyBase.Describe()
    End Function
End Class

Public Class Edition
    Inherits Book
    Public NotOverridable Overrides Function Describe() As String
        Return "Edition " & MyBase.Describe()
    End Function
    Public Shadows Function Plain() As String
        Return "shadowed"
    End Function
End Class

Public NotInheritable Class Rare
    Inherits Edition
End Class

Module Program
    Sub Report(t As Type, name As String)
        Dim m As MethodInfo = t.GetMethod(name, BindingFlags.Public Or BindingFlags.Instance Or BindingFlags.Static Or BindingFlags.DeclaredOnly)
        If m Is Nothing Then
            Console.WriteLine(t.Name & "." & name & ": not declared here")
            Return
        End If
        Console.WriteLine(t.Name & "." & name &
            ": IsVirtual=" & m.IsVirtual & " IsAbstract=" & m.IsAbstract & " IsFinal=" & m.IsFinal &
            " IsHideBySig=" & m.IsHideBySig & " NewSlot=" & ((m.Attributes And MethodAttributes.NewSlot) <> 0) &
            " IsStatic=" & m.IsStatic)
    End Sub

    Sub Main()
        Console.WriteLine("CatalogItem  IsAbstract=" & GetType(CatalogItem).IsAbstract & " IsSealed=" & GetType(CatalogItem).IsSealed)
        Console.WriteLine("Book         IsAbstract=" & GetType(Book).IsAbstract & " IsSealed=" & GetType(Book).IsSealed)
        Console.WriteLine("Rare         IsAbstract=" & GetType(Rare).IsAbstract & " IsSealed=" & GetType(Rare).IsSealed)
        Console.WriteLine("Module type  IsAbstract=" & GetType(Program).IsAbstract & " IsSealed=" & GetType(Program).IsSealed)
        Console.WriteLine()
        Report(GetType(CatalogItem), "Describe")
        Report(GetType(CatalogItem), "get_Kind")
        Report(GetType(CatalogItem), "Plain")
        Report(GetType(CatalogItem), "Create")
        Report(GetType(Book), "Describe")
        Report(GetType(Edition), "Describe")
        Report(GetType(Edition), "Plain")
        Report(GetType(Rare), "Describe")
        Console.WriteLine()
        Dim m = GetType(Rare).GetMethod("Describe")
        Console.WriteLine("Rare.Describe base definition declared in: " & m.GetBaseDefinition().DeclaringType.Name)
    End Sub
End Module
CatalogItem  IsAbstract=True IsSealed=False
Book         IsAbstract=False IsSealed=False
Rare         IsAbstract=False IsSealed=True
Module type  IsAbstract=False IsSealed=True

CatalogItem.Describe: IsVirtual=True IsAbstract=False IsFinal=False IsHideBySig=False NewSlot=True IsStatic=False
CatalogItem.get_Kind: IsVirtual=True IsAbstract=True IsFinal=False IsHideBySig=False NewSlot=True IsStatic=False
CatalogItem.Plain: IsVirtual=False IsAbstract=False IsFinal=False IsHideBySig=False NewSlot=False IsStatic=False
CatalogItem.Create: IsVirtual=False IsAbstract=False IsFinal=False IsHideBySig=False NewSlot=False IsStatic=True
Book.Describe: IsVirtual=True IsAbstract=False IsFinal=False IsHideBySig=True NewSlot=False IsStatic=False
Edition.Describe: IsVirtual=True IsAbstract=False IsFinal=True IsHideBySig=True NewSlot=False IsStatic=False
Edition.Plain: IsVirtual=False IsAbstract=False IsFinal=False IsHideBySig=False NewSlot=False IsStatic=False
Rare.Describe: not declared here

Rare.Describe base definition declared in: CatalogItem
Visual Basic What the output shows

MustInherit class

IsAbstract=True

NotInheritable class, Module

IsSealed=True (a module is a sealed class)

Overridable

IsVirtual=True, NewSlot=True: a new virtual slot, IsFinal=False

MustOverride property accessor

IsVirtual=True, IsAbstract=True

plain method

IsVirtual=False; Shared also IsStatic=True

Overrides

IsVirtual=True, NewSlot=False: reuses the slot of the base member, and IsFinal=False, so it can be overridden again

NotOverridable Overrides

IsFinal=True

Shadows

a plain non-virtual method (Edition.Plain has IsVirtual=False)

IsHideBySig is False on the plain methods and True on the overrides. That is consistent with the behaviour described in the next section: a Visual Basic member that hides an inherited one hides every overload of that name, unless it is an override or declared Overloads. The last line shows GetBaseDefinition, which follows the chain of overrides to the declaration that introduced the virtual slot.

Shadows versus Overloads versus Overrides

Three keywords decide what a derived-class member with the same name as an inherited one means.

  • Overrides replaces an inherited member: the slot is shared, and the call is resolved at run time from the real class of the object.

  • Overloads adds a member with a different signature, and keeps every inherited member of that name visible.

  • Shadows introduces a new member that hides the inherited one by name, with no relation to it. A call through a base-typed reference still reaches the base member, and a call through a derived-typed reference reaches the new one.

Option Strict On

Public Class CatalogItem
    Public Overridable Function Label() As String
        Return "CatalogItem.Label"
    End Function

    Public Function Fixed() As String
        Return "CatalogItem.Fixed"
    End Function

    Public Overridable Function Shelf() As String
        Return "CatalogItem.Shelf"
    End Function

    Public Sub Show(n As Integer)
        Console.WriteLine("CatalogItem.Show(Integer) " & n)
    End Sub

    Public Sub Show(s As String)
        Console.WriteLine("CatalogItem.Show(String) " & s)
    End Sub
End Class

Public Class Book
    Inherits CatalogItem

    Public Overrides Function Label() As String
        Return "Book.Label"
    End Function

    Public Shadows Function Fixed() As String
        Return "Book.Fixed"
    End Function

    Public Shadows Function Shelf() As String
        Return "Book.Shelf"
    End Function

    Public Overloads Sub Show(d As Double)
        Console.WriteLine("Book.Show(Double) " & d)
    End Sub
End Class

Public Class Atlas
    Inherits CatalogItem

    ' Shadows hides every Show of CatalogItem by name; without it the compiler warns (BC40003).
    Public Shadows Sub Show(d As Double)
        Console.WriteLine("Atlas.Show(Double) " & d)
    End Sub
End Class

Module Program
    Sub Main()
        Dim b As New Book()
        Dim viaBase As CatalogItem = b

        Console.WriteLine("Overrides: " & viaBase.Label() & " | " & b.Label())
        Console.WriteLine("Shadows  : " & viaBase.Fixed() & " | " & b.Fixed())
        Console.WriteLine("Shadows over Overridable: " & viaBase.Shelf() & " | " & b.Shelf())

        b.Show(1)
        b.Show("two")
        b.Show(3.5)

        Dim a As New Atlas()
        a.Show(4.5)
        a.Show(5)   ' Integer widens to Double: the base overloads are not even considered
        CType(a, CatalogItem).Show(5)
    End Sub
End Module
Overrides: Book.Label | Book.Label
Shadows  : CatalogItem.Fixed | Book.Fixed
Shadows over Overridable: CatalogItem.Shelf | Book.Shelf
CatalogItem.Show(Integer) 1
CatalogItem.Show(String) two
Book.Show(Double) 3.5
Atlas.Show(Double) 4.5
Atlas.Show(Double) 5
CatalogItem.Show(Integer) 5

Reading the output line by line.

  1. Label is overridden: the base-typed and the derived-typed variable print the same text. This is polymorphism.

  2. Fixed is shadowed. The same object prints CatalogItem.Fixed through the base variable and Book.Fixed through the derived one. Shadowing is resolved at compile time from the variable’s static type, which makes it a source of surprises: two variables for one object see two different methods.

  3. Shelf shadows an overridable method. It compiles without a warning because Shadows is explicit, and the base variable still reaches CatalogItem.Shelf. This is almost never what the author wanted; compare the warning BC40005 below.

  4. Show(Double) in Book is declared Overloads, so all three overloads are visible on a Book: b.Show(1) picks Show(Integer) from the base class.

  5. Atlas.Show(Double) is declared Shadows and therefore hides both Show overloads of CatalogItem: a.Show(5) converts the argument to Double and calls Atlas.Show(Double), never Show(Integer). Only a cast to the base type reaches the hidden overload.

The warnings you get for forgetting the keyword

A member that has the same name as an inherited one but no Shadows, Overloads or Overrides keyword hides it anyway (Visual Basic hides by name), and the compiler warns. The program below was built with warnings not treated as errors, to show the messages, and then run.

Option Strict On

Public Class CatalogItem
    Public Overridable Function Label() As String
        Return "item"
    End Function
    Public Function Fixed() As String
        Return "fixed"
    End Function
    Public Sub Show(n As Integer)
    End Sub
    Public Shared Function Create() As String
        Return "create"
    End Function
    Public Shared Counter As Integer
End Class

Public Class Book
    Inherits CatalogItem

    Public Function Label() As String           ' BC40005: shadows an overridable method
        Return "book"
    End Function

    Public Function Fixed() As String           ' BC40004: conflicts with the base member
        Return "book fixed"
    End Function

    Public Sub Show(s As String)                ' BC40003: hides the base overload by name
    End Sub
End Class

Module Program
    Sub Main()
        Dim b As New Book()
        Console.WriteLine(b.Create())             ' BC42025: Shared member accessed through an instance
        Console.WriteLine(Book.Create())          ' fine: Shared members are inherited
        Book.Counter = 3
        Console.WriteLine(CatalogItem.Counter)
    End Sub
End Module
Program.vb(21,21): warning BC40005: function 'Label' shadows an overridable method in the base class 'CatalogItem'. To override the base method, this method must be declared 'Overrides'.
Program.vb(25,21): warning BC40003: function 'Fixed' shadows an overloadable member declared in the base class 'CatalogItem'.  If you want to overload the base method, this method must be declared 'Overloads'.
Program.vb(29,16): warning BC40003: sub 'Show' shadows an overloadable member declared in the base class 'CatalogItem'.  If you want to overload the base method, this method must be declared 'Overloads'.
Program.vb(36,27): warning BC42025: Access of shared member, constant member, enum member or nested type through an instance; qualifying expression will not be evaluated.
create
create
3
  • BC40005: a method that has the name and signature of an Overridable base method, and no Overrides or Shadows. Nearly always a forgotten Overrides. The method is not an override.

  • BC40003: a method that hides inherited overloadable members without Overloads or Shadows. Either add Overloads to keep the base overloads visible, or Shadows if hiding is intended. Fixed is reported here too, although it has the same signature, because the base method is not overridable.

  • BC42025: b.Create() uses an instance to reach a Shared member, and the instance expression is not evaluated. Call Book.Create() or CatalogItem.Create(). The program also shows that Shared members are inherited: Book.Create() and Book.Counter reach the members of CatalogItem, and Book.Counter = 3 assigns the one shared field.

  • Treat these warnings as errors (TreatWarningsAsErrors or an .editorconfig severity); they are all real defects or unclear intent.

Overloads Overrides, nested types and properties

Overloads and Overrides can be combined, and Shadows also hides nested types and properties.

Option Strict On

Public Class Formatter
    Public Overridable Function Format(value As Integer) As String
        Return "int:" & value
    End Function

    Public Overridable Function Format(value As String) As String
        Return "str:" & value
    End Function

    Public Class Options
        Public Property Width As Integer = 10
    End Class

    Public Overridable Property Prefix As String = ">"
End Class

Public Class LoudFormatter : Inherits Formatter     ' Inherits after a colon on the same line
    ' Overloads Overrides: overrides one overload and keeps seeing the others
    Public Overloads Overrides Function Format(value As Integer) As String
        Return "LOUD " & MyBase.Format(value)
    End Function

    ' Shadows a nested type of the base class
    Public Shadows Class Options
        Public Property Width As Integer = 99
    End Class

    Public Shadows ReadOnly Property Prefix As String = "!"
End Class

Module Program
    Sub Main()
        Dim f As New LoudFormatter()
        Console.WriteLine(f.Format(1))
        Console.WriteLine(f.Format("a"))
        Console.WriteLine(New Formatter.Options().Width & " " & New LoudFormatter.Options().Width)
        Dim asBase As Formatter = f
        Console.WriteLine(asBase.Prefix & " " & f.Prefix)
    End Sub
End Module
LOUD int:1
str:a
10 99
> !

Source: Overloads

f.Format("a") still reaches the String overload of Formatter, because only the Integer overload was replaced. Shadows Class Options makes LoudFormatter.Options a different class from Formatter.Options, and Shadows ReadOnly Property Prefix is not seen through a Formatter variable (> against !). In C#, the equivalent of Shadows is new, and a C# member that merely has the same name as an inherited one hides only a member with the same signature: the other overloads stay visible, which is the opposite default.

MyBase, MyClass and Me

  • Me is the current object, of its run-time class. A call Me.Name() of an Overridable member reaches the most derived override (virtual dispatch).

  • MyBase accesses members of the immediate base class. MyBase.Name() is a non-virtual call of the base implementation, whatever the object is. It is how an override extends the base behaviour. MyBase is not a value: it must be followed by a dot and a member (Return MyBase is BC32027, see the error listing below).

  • MyClass calls a member of the class where the call is written, ignoring overrides in derived classes: a non-virtual call of an Overridable method. C# has no equivalent. Use it to guarantee that the algorithm in the base class always uses the base implementation of a helper, or when a constructor has to call an overridable member without reaching the derived class.

  • Me.New(...), MyClass.New(...) and MyBase.New(...) are constructor calls; each must be the first statement of an instance constructor.

Option Strict On

Public Class LoanPolicy
    Public Sub New()
        Me.New(14)                         ' constructor chaining inside one class
    End Sub

    Public Sub New(days As Integer)
        LoanDays = days
        Console.WriteLine("LoanPolicy.New(" & days & "): MyClass.Name() = " & MyClass.Name() & ", Me.Name() = " & Me.Name())
    End Sub

    Public ReadOnly Property LoanDays As Integer

    Public Overridable Function Name() As String
        Return "standard"
    End Function

    Public Function ViaMe() As String
        Return "ViaMe -> " & Me.Name()
    End Function

    Public Function ViaMyClass() As String
        Return "ViaMyClass -> " & MyClass.Name()
    End Function
End Class

Public Class StudentPolicy
    Inherits LoanPolicy

    Public Sub New()
        MyBase.New(28)
    End Sub

    Public Overrides Function Name() As String
        Return "student"
    End Function
End Class

Module Program
    Sub Main()
        Dim p As New StudentPolicy()
        Console.WriteLine(p.ViaMe())
        Console.WriteLine(p.ViaMyClass())
        Console.WriteLine(p.LoanDays)
        Dim q As New LoanPolicy()
        Console.WriteLine(q.LoanDays)
    End Sub
End Module
LoanPolicy.New(28): MyClass.Name() = standard, Me.Name() = student
ViaMe -> student
ViaMyClass -> standard
28
LoanPolicy.New(14): MyClass.Name() = standard, Me.Name() = standard
14

The first line of output comes from the constructor LoanPolicy.New(Integer) that StudentPolicy reached through MyBase.New(28). At that point the object is already a StudentPolicy: Me.Name() returned student, MyClass.Name() returned standard. In the last two lines the plain LoanPolicy constructor chained to Me.New(14).

Constructors in a hierarchy

  • A derived-class constructor must call a base constructor. If its first statement is not MyBase.New(...) (or Me.New(...)/MyClass.New(...)), the compiler inserts MyBase.New(). When the base class has no accessible parameterless constructor, the compiler refuses: BC30387 for a class without any Sub New, BC30148 for a constructor that does not start with an explicit call.

  • A constructor call is valid only as the first statement (BC30282).

  • A class without constructors gets a default Public Sub New(); a MustInherit class gets a Protected one. The code below confirms it by reflection, together with two other defaults that differ from C#: a method without an access modifier is Public, a field declared with Dim is Private, and a top-level type without a modifier is Friend.

Option Strict On
Imports System.Reflection

Public MustInherit Class AbstractItem
End Class

Public Class ConcreteItem
End Class

Class Holder
    Dim hidden As Integer
    Sub NoModifier()
    End Sub
End Class

Module Program
    Sub Show(t As Type)
        For Each c In t.GetConstructors(BindingFlags.Instance Or BindingFlags.Public Or BindingFlags.NonPublic)
            Console.WriteLine(t.Name & " constructor: IsPublic=" & c.IsPublic & " IsFamily(Protected)=" & c.IsFamily)
        Next
    End Sub

    Sub Main()
        Show(GetType(AbstractItem))
        Show(GetType(ConcreteItem))
        Dim f = GetType(Holder).GetField("hidden", BindingFlags.Instance Or BindingFlags.NonPublic Or BindingFlags.Public)
        Console.WriteLine("Dim field IsPrivate=" & f.IsPrivate)
        Console.WriteLine("Sub without modifier IsPublic=" & GetType(Holder).GetMethod("NoModifier").IsPublic)
        Console.WriteLine("Top-level Class without modifier: IsNotPublic=" & GetType(Holder).IsNotPublic)
    End Sub
End Module
AbstractItem constructor: IsPublic=False IsFamily(Protected)=True
ConcreteItem constructor: IsPublic=True IsFamily(Protected)=False
Dim field IsPrivate=True
Sub without modifier IsPublic=True
Top-level Class without modifier: IsNotPublic=True

Source: Class Statement

Initialization order, and a trap

The order in which field initializers and constructors run is the most important difference between Visual Basic and C#:

  • In C# the field initializers of the derived class run before the base constructor.

  • In Visual Basic the field initializers of the derived class run after the base constructor returns, right after the MyBase.New call.

This matters when a base constructor calls an overridable member: in Visual Basic the derived override sees the default values of the derived fields.

Option Strict On

Public Class Base
    Private ReadOnly _label As String = "base-field"

    Public Sub New()
        Console.WriteLine("Base.New: start")
        Console.WriteLine("Base.New: Describe() says: " & Describe())
        Console.WriteLine("Base.New: end")
    End Sub

    Public Overridable Function Describe() As String
        Return "Base.Describe (" & _label & ")"
    End Function
End Class

Public Class Derived
    Inherits Base

    Private ReadOnly _name As String = InitName("field initializer")
    Private _count As Integer = 5

    Public Sub New()
        MyBase.New()
        Console.WriteLine("Derived.New: after MyBase.New, _count = " & _count)
    End Sub

    Private Shared Function InitName(source As String) As String
        Console.WriteLine("Derived: initializing _name from " & source)
        Return "derived-field"
    End Function

    Public Overrides Function Describe() As String
        Return "Derived.Describe (_name = " & If(_name, "<Nothing>") & ", _count = " & _count & ")"
    End Function
End Class

Module Program
    Sub Main()
        Dim d As New Derived()
        Console.WriteLine(d.Describe())
    End Sub
End Module
Base.New: start
Base.New: Describe() says: Derived.Describe (_name = <Nothing>, _count = 0)
Base.New: end
Derived: initializing _name from field initializer
Derived.New: after MyBase.New, _count = 5
Derived.Describe (_name = derived-field, _count = 5)

The override ran inside the base constructor, before the initializers of Derived: _name was Nothing and _count was 0. Then the initializers ran (Derived: initializing _name ...), and by the time Derived.New continued the values were set. The equivalent C# program, which was built and run in the same container, prints the field initializer line first, and the override sees the initialized fields:

var d = new Derived();
Console.WriteLine(d.Describe());

public class Base
{
    private readonly string _label = "base-field";

    public Base()
    {
        Console.WriteLine("Base.New: start");
        Console.WriteLine("Base.New: Describe() says: " + Describe());
        Console.WriteLine("Base.New: end");
    }

    public virtual string Describe() => "Base.Describe (" + _label + ")";
}

public class Derived : Base
{
    private readonly string? _name = InitName("field initializer");
    private int _count = 5;

    public Derived() : base()
    {
        Console.WriteLine("Derived.New: after base(), _count = " + _count);
    }

    private static string InitName(string source)
    {
        Console.WriteLine("Derived: initializing _name from " + source);
        return "derived-field";
    }

    public override string Describe() => "Derived.Describe (_name = " + (_name ?? "<null>") + ", _count = " + _count + ")";
}
Derived: initializing _name from field initializer
Base.New: start
Base.New: Describe() says: Derived.Describe (_name = derived-field, _count = 5)
Base.New: end
Derived.New: after base(), _count = 5
Derived.Describe (_name = derived-field, _count = 5)

In both languages the rule for the programmer is the same: do not call an overridable member from a constructor. The override would run on an object whose derived part is not yet built, and the order differs between the languages. If the call is unavoidable, make it through MyClass.Name() (so that the base version runs), or move the work into an explicit Initialize method that the caller invokes after construction, or use a factory.

Constructor errors

The next file collects errors in bodies and constructors. It was built with TreatWarningsAsErrors off, as the file is meant to fail. Declaration errors appeared in the earlier listing; these appear once the declarations are valid.

Option Strict On

Public MustInherit Class CatalogItem
    Public Property Title As String = ""
    Protected Sub New()
    End Sub
    Public MustOverride ReadOnly Property Kind As String
    Protected Function Secret() As String
        Return "secret"
    End Function
End Class

Public Class Book
    Inherits CatalogItem
    Public Overrides ReadOnly Property Kind As String = "Book"
    Public Function Peek(other As CatalogItem) As String
        Return other.Secret()                                       ' E1: Protected, through a base-typed reference
    End Function
    Public Function CallAbstract() As String
        Return MyBase.Kind                                          ' E2: MyBase cannot call a MustOverride member
    End Function
End Class

Public Class NeedsArgs
    Public Sub New(x As Integer)
    End Sub
End Class

Public Class ChildOfNeedsArgs               ' E3: no parameterless base constructor
    Inherits NeedsArgs
End Class

Public Class ChildWithNew
    Inherits NeedsArgs
    Public Sub New()                        ' E4: first statement must call MyBase.New
        Console.WriteLine("hello")
    End Sub
End Class

Public Class LateCall
    Inherits NeedsArgs
    Public Sub New()
        MyBase.New(1)
        MyBase.New(2)                       ' E5: the constructor call is allowed once, first
    End Sub
End Class

Public Class Report
    Public Event Done As EventHandler
End Class

Public Class SubReport
    Inherits Report
    Public Sub Finish()
        RaiseEvent Done(Me, EventArgs.Empty)       ' E6: only the declaring class can raise an event
    End Sub
End Class

Public MustInherit Class Abstracta
    Public MustOverride Function Id() As Integer
    Public Sub Test()
        Console.WriteLine(MyClass.Id())            ' E7: MyClass cannot call a MustOverride member
    End Sub
End Class

Module Program
    Sub Main()
        Dim item As New CatalogItem()                               ' E8: MustInherit
        Dim list As New List(Of Book)
        Dim all As List(Of CatalogItem) = list                      ' E9: List(Of T) is invariant
        Dim b As New Book()
        Dim c As CatalogItem = b
        Dim back As Book = c                                        ' E10: narrowing needs a cast
        Dim s As String = "text"
        Console.WriteLine(TypeOf s Is Book)                         ' E11: can never be a Book
        Console.WriteLine(b.Secret())                               ' E12: Protected, outside the hierarchy
    End Sub
End Module
Program.vb(17,16): error BC30390: 'CatalogItem.Protected Function Secret() As String' is not accessible in this context because it is 'Protected'.
Program.vb(20,16): error BC30399: 'MyBase' cannot be used with method 'Public MustOverride ReadOnly Property Kind As String' because it is declared 'MustOverride'.
Program.vb(29,14): error BC30387: Class 'ChildOfNeedsArgs' must declare a 'Sub New' because its base class 'NeedsArgs' does not have an accessible 'Sub New' that can be called with no arguments.
Program.vb(35,16): error BC30148: First statement of this 'Sub New' must be a call to 'MyBase.New' or 'MyClass.New' because base class 'NeedsArgs' of 'ChildWithNew' does not have an accessible 'Sub New' that can be called with no arguments.
Program.vb(44,9): error BC30282: Constructor call is valid only as the first statement in an instance constructor.
Program.vb(55,20): error BC30029: Derived classes cannot raise base class events.
Program.vb(62,27): error BC30614: 'MustOverride' method 'Public MustOverride Function Id() As Integer' cannot be called with 'MyClass'.
Program.vb(68,21): error BC30569: 'New' cannot be used on a class that is declared 'MustInherit'.
Program.vb(70,43): error BC36756: 'List(Of Book)' cannot be converted to 'List(Of CatalogItem)'. Consider using 'IEnumerable(Of CatalogItem)' instead.
Program.vb(73,28): error BC30512: Option Strict On disallows implicit conversions from 'CatalogItem' to 'Book'.
Program.vb(75,27): error BC31430: Expression of type 'String' can never be of type 'Book'.
Program.vb(76,27): error BC30390: 'CatalogItem.Protected Function Secret() As String' is not accessible in this context because it is 'Protected'.

Source: Sub Statement

Label Error Meaning

E1

BC30390

a Protected member is accessible through Me, MyBase or an instance of the derived class, not through a reference typed as the base class

E2

BC30399

MyBase cannot call a MustOverride member

E3

BC30387

a derived class without a constructor, whose base class has none that takes no arguments

E4

BC30148

a constructor of such a derived class that does not start with MyBase.New(...)

E5

BC30282

a second constructor call, or one that is not first

E6

BC30029

RaiseEvent on an event of the base class (see Events in a hierarchy)

E7

BC30614

MyClass cannot call a MustOverride member

E8

BC30569

New on a MustInherit class

E9

BC36756

List(Of Book) is not a List(Of CatalogItem) (see Variance and collections)

E10

BC30512

an implicit narrowing conversion under Option Strict On (see Type tests and casts)

E11

BC31430

TypeOf s Is Book where s is a String: the types are unrelated

E12

BC30390

a Protected member used outside the hierarchy

Two further mistakes are syntax errors, which the compiler reports before it looks at meaning, so they are in their own file:

Option Strict On

Public Class Book
    Public Property Title As String = ""

    Public Function AsValue() As Object
        Return MyBase                       ' E2: MyBase is not a value
    End Function
End Class

Module Program
    Sub Main()
        Dim o As Object = New Book With {.Title = "Refactoring"}
        If TypeOf o Is Book b Then          ' E1: Visual Basic has no type patterns
            Console.WriteLine(b.Title)
        End If
    End Sub
End Module
Program.vb(14,29): error BC30205: End of statement expected.
Program.vb(7,16): error BC32027: 'MyBase' must be followed by '.' and an identifier.

Source: TypeOf Operator

E1 shows that Visual Basic 17.13 has no type pattern (If x Is Book b in C#): the compiler stops after Book and expects the end of the statement. The cast has to be a separate statement, as in the next sections. E2 shows that MyBase cannot be returned or passed around as a value.

Run-time and compile-time polymorphism

Polymorphism in Visual Basic comes in two forms, and mixing them up is a classic source of confusion.

  • Run-time polymorphism (also called overriding, subtype or dynamic polymorphism): the same call on a base-typed reference runs the method of the object’s real class. It needs Overridable/Overrides (or an interface implementation) and is decided when the program runs.

  • Compile-time polymorphism (overloading): several procedures with the same name but different parameters; the compiler picks one from the static types of the arguments. The object that a base-typed variable holds is not considered.

Option Strict On

Public MustInherit Class CatalogItem
    Public Property Title As String = ""
    Public Overridable Function Describe() As String
        Return "item"
    End Function
End Class

Public Class Book
    Inherits CatalogItem
    Public Overrides Function Describe() As String
        Return "book"
    End Function
End Class

Module Program
    ' Overload resolution happens at compile time, from the static types of the arguments.
    Sub Print(item As CatalogItem)
        Console.WriteLine("Print(CatalogItem): " & item.Describe())
    End Sub

    Sub Print(book As Book)
        Console.WriteLine("Print(Book): " & book.Describe())
    End Sub

    Sub Main()
        Dim b As New Book()
        Dim asItem As CatalogItem = b

        Print(b)                    ' static type Book
        Print(asItem)               ' static type CatalogItem, although the object is a Book
        Print(DirectCast(asItem, Book))
    End Sub
End Module
Print(Book): book
Print(CatalogItem): book
Print(Book): book

Print(asItem) calls Print(CatalogItem) although the object is a Book, and inside it item.Describe() is dispatched at run time and still prints book. The choice of overload was made at compile time; the choice of Describe at run time. To select the overload from the real class, cast first (DirectCast(asItem, Book)), or move the behaviour into an overridable member of the hierarchy, which is the polymorphic solution and does not need a type test.

With Option Strict Off, a variable typed Object is late bound: the member is looked up when the program runs, from the real class. That is a third form of dispatch, and it is why the late-bound code of old tutorials seems to do polymorphism with no inheritance at all (see Early and Late Binding). Keep Option Strict On and use an interface or a base class instead.

Type tests and casts

Expression Meaning Result when it does not match

TypeOf x Is T

True if x is not Nothing and its class is T or derived from T (or implements the interface T)

False

TypeOf x IsNot T

Not (TypeOf x Is T)

True

x.GetType() = GetType(T)

Exact class match; a derived class is not a match

False

GetType(T).IsInstanceOfType(x)

Same as TypeOf x Is T, for a Type that is only known at run time

False

DirectCast(x, T)

Reference conversion with a run-time check, no conversion functions

InvalidCastException

TryCast(x, T)

Like DirectCast, but Nothing instead of the exception; T must be a reference type or a Nullable

Nothing

CType(x, T)

Casts, plus every user-defined or built-in conversion (Narrowing/Widening operators)

InvalidCastException, or the exception of the conversion

x Is y

Reference identity (the same object); does not test a type

False

Option Strict On

Public MustInherit Class CatalogItem
    Public Property Title As String = ""
    Public MustOverride ReadOnly Property Kind As String
End Class

Public Class Book
    Inherits CatalogItem
    Public Property Pages As Integer
    Public Overrides ReadOnly Property Kind As String = "Book"
End Class

Public Class Textbook
    Inherits Book
    Public Property Course As String = ""
End Class

Public Class Magazine
    Inherits CatalogItem
    Public Property IssueNumber As Integer
    Public Overrides ReadOnly Property Kind As String = "Magazine"
End Class

Module Program
    Function Summarize(item As CatalogItem) As String
        If TypeOf item Is Textbook Then
            Return "textbook for " & DirectCast(item, Textbook).Course
        ElseIf TypeOf item Is Book Then
            Return "book with " & DirectCast(item, Book).Pages & " pages"
        ElseIf TypeOf item Is Magazine Then
            Return "issue " & DirectCast(item, Magazine).IssueNumber
        End If
        Return "unknown"
    End Function

    Sub Main()
        Dim items As CatalogItem() = {
            New Book With {.Title = "Clean Code", .Pages = 464},
            New Textbook With {.Title = "Algorithms", .Course = "CS101", .Pages = 900},
            New Magazine With {.Title = "Byte", .IssueNumber = 7}
        }

        For Each item In items
            Console.WriteLine(item.Title & ": " & Summarize(item))
        Next
        Console.WriteLine()

        Dim t As CatalogItem = items(1)
        Console.WriteLine("TypeOf t Is Book       : " & (TypeOf t Is Book))
        Console.WriteLine("TypeOf t Is Textbook   : " & (TypeOf t Is Textbook))
        Console.WriteLine("t.GetType() = GetType(Book)    : " & (t.GetType() = GetType(Book)))
        Console.WriteLine("GetType(Book).IsInstanceOfType(t): " & GetType(Book).IsInstanceOfType(t))
        Console.WriteLine("TypeOf t IsNot Magazine: " & (TypeOf t IsNot Magazine))

        Dim nothingItem As CatalogItem = Nothing
        Console.WriteLine("TypeOf Nothing Is Book : " & (TypeOf nothingItem Is Book))
        Console.WriteLine("TryCast(Nothing)       : " & (TryCast(nothingItem, Book) Is Nothing))
        Console.WriteLine()

        Dim asBook As Book = TryCast(items(2), Book)
        Console.WriteLine("TryCast(magazine, Book) Is Nothing: " & (asBook Is Nothing))
        Dim viaCType As Book = CType(items(1), Book)
        Console.WriteLine("CType(textbook, Book)   : " & viaCType.Title)

        Try
            Dim wrong As Book = DirectCast(items(2), Book)
            Console.WriteLine(wrong.Title)
        Catch ex As InvalidCastException
            Console.WriteLine(ex.GetType().Name & ": " & ex.Message)
        End Try

        Select Case True
            Case TypeOf t Is Magazine : Console.WriteLine("select: magazine")
            Case TypeOf t Is Book : Console.WriteLine("select: book")
            Case Else : Console.WriteLine("select: other")
        End Select
    End Sub
End Module
Clean Code: book with 464 pages
Algorithms: textbook for CS101
Byte: issue 7

TypeOf t Is Book       : True
TypeOf t Is Textbook   : True
t.GetType() = GetType(Book)    : False
GetType(Book).IsInstanceOfType(t): True
TypeOf t IsNot Magazine: True
TypeOf Nothing Is Book : False
TryCast(Nothing)       : True

TryCast(magazine, Book) Is Nothing: True
CType(textbook, Book)   : Algorithms
InvalidCastException: Unable to cast object of type 'Shelf.Magazine' to type 'Shelf.Book'.
select: book
  • The Textbook test comes first in Summarize: TypeOf item Is Book is also True for a Textbook, so the most specific class must be tested first. GetType(Book) equality is False for the same object. Pick the operator that matches the intent: TypeOf for "is usable as", GetType equality for "is exactly".

  • TypeOf Nothing Is Book is False and TryCast of Nothing is Nothing; no NullReferenceException.

  • The message of the InvalidCastException names the full class names (Shelf.Magazine, Shelf.Book). The root namespace is part of the name.

  • Select Case True with Case TypeOf t Is Magazine is the nearest thing to a type switch: the cases are tested in order, so again, derived types before base types.

  • DirectCast after a TypeOf test is correct but tests twice; TryCast followed by an Is Nothing check does both in one step and is the idiom when the cast result is needed. TypeOf alone is the idiom when only the answer is needed.

  • The need for many TypeOf tests on the same hierarchy is usually a smell: add an overridable member to the hierarchy (the Describe of this page) so that each class does its own part.

See Type Conversions for the complete list of conversion operators and the Option Strict rules that make the narrowing assignment of E10 an error.

Variance and collections

A Book can be used wherever a CatalogItem is expected, but collections of books are not collections of items in general. Visual Basic supports the .NET variance rules for generic interfaces and delegates (since Visual Basic 2010), and the array covariance of the CLR.

Option Strict On

Public MustInherit Class CatalogItem
    Public Property Title As String = ""
End Class

Public Class Book
    Inherits CatalogItem
End Class

Public Class Magazine
    Inherits CatalogItem
End Class

Module Program
    Sub PrintTitles(items As IEnumerable(Of CatalogItem))
        For Each item In items
            Console.WriteLine("  " & item.Title)
        Next
    End Sub

    Sub Main()
        Dim books As New List(Of Book) From {New Book With {.Title = "Refactoring"}}

        ' Covariance: IEnumerable(Of Book) is an IEnumerable(Of CatalogItem).
        Console.WriteLine("IEnumerable(Of T) is covariant:")
        PrintTitles(books)

        ' Contravariance: an Action(Of CatalogItem) accepts a Book.
        Dim printItem As Action(Of CatalogItem) = Sub(i) Console.WriteLine("  action got " & i.Title)
        Dim printBook As Action(Of Book) = printItem
        printBook(books(0))

        ' Arrays are covariant too, and unsafe.
        Dim bookArray As Book() = {New Book With {.Title = "Array book"}}
        Dim itemArray As CatalogItem() = bookArray
        Console.WriteLine("array covariance: " & itemArray(0).Title)
        Try
            itemArray(0) = New Magazine With {.Title = "Byte"}
        Catch ex As ArrayTypeMismatchException
            Console.WriteLine("store failed: " & ex.GetType().Name)
        End Try

        ' List(Of T) is invariant: Dim x As List(Of CatalogItem) = books fails with BC36756.
        Dim copy As New List(Of CatalogItem)(books)
        copy.Add(New Magazine With {.Title = "Byte"})
        Console.WriteLine("copy has " & copy.Count & " items, books still has " & books.Count)
    End Sub
End Module
IEnumerable(Of T) is covariant:
  Refactoring
  action got Refactoring
array covariance: Array book
store failed: ArrayTypeMismatchException
copy has 2 items, books still has 1
  • IEnumerable(Of Book) converts to IEnumerable(Of CatalogItem): the interface is declared Out T (covariant), because items only come out. Action(Of CatalogItem) converts to Action(Of Book): the delegate is declared In T (contravariant), because the argument goes in.

  • List(Of Book) does not convert to List(Of CatalogItem) (BC36756): a class cannot be variant, because the list would allow Add(New Magazine ...) through the item-typed view. The message suggests IEnumerable(Of CatalogItem). Copying (New List(Of CatalogItem)(books)) makes a separate list.

  • Arrays are covariant, which compiles and is unsafe: storing a Magazine through a CatalogItem() that is really a Book() throws ArrayTypeMismatchException at run time. Prefer IReadOnlyList(Of T)/IEnumerable(Of T) for the parameters of methods that only read.

  • Your own variant interfaces use Out/In in the declaration (see Generics).

Access modifiers and inheritance

Modifier Visible to

Public

every caller

Protected

the declaring class and its derived classes, in any assembly; through a derived-typed reference only

Friend

every type in the same assembly (C# internal)

Protected Friend

the union: derived classes in any assembly, plus every type in the same assembly (C# protected internal)

Private Protected

the intersection: derived classes in the same assembly only (C# private protected; Visual Basic 15.5)

Private

the declaring type, including its nested types

  • A member without an access modifier is Public (members of types), a Dim field is Private, and a top-level type is Friend. C# defaults to private and internal.

  • An override must have the same access as the member it overrides (BC30266).

  • The Protected rule is subtle: inside Book, Me.Secret(), MyBase.Secret() and otherBook.Secret() compile, but otherItem.Secret() with otherItem As CatalogItem does not (BC30390, E1 in the constructor-errors listing), because the other object may be a Magazine, a sibling class whose protected members Book has no right to use.

  • Keep the protected surface small: it is part of the public contract for every subclass, and it is as hard to change as a public member. The framework design guidelines list the rules.

Events in a hierarchy

An event can be raised only by the class that declares it. A derived class that wants to raise it cannot (BC30029); the base class offers a Protected Overridable Sub OnXxx method instead, which raises the event, and the derived class overrides it to add behaviour before or after. This is the pattern of the .NET class libraries.

Option Strict On

Public Class LoanEventArgs
    Inherits EventArgs
    Public ReadOnly Property Borrower As String
    Public Sub New(borrower As String)
        Me.Borrower = borrower
    End Sub
End Class

Public Class Loan
    Public Event Returned As EventHandler(Of LoanEventArgs)
    Public ReadOnly Property Borrower As String

    Public Sub New(borrower As String)
        Me.Borrower = borrower
    End Sub

    Public Sub [Return]()
        OnReturned(New LoanEventArgs(Borrower))
    End Sub

    ' The standard pattern: the event can be raised only inside the declaring class,
    ' so the class offers a Protected Overridable method for derived classes.
    Protected Overridable Sub OnReturned(e As LoanEventArgs)
        RaiseEvent Returned(Me, e)
    End Sub
End Class

Public Class AuditedLoan
    Inherits Loan

    Public Sub New(borrower As String)
        MyBase.New(borrower)
    End Sub

    Protected Overrides Sub OnReturned(e As LoanEventArgs)
        Console.WriteLine("audit: " & e.Borrower & " returned the item")
        MyBase.OnReturned(e)           ' forget this call and no handler is ever notified
    End Sub
End Class

Public Class Reader
    Public Sub Attach(loan As Loan)
        AddHandler loan.Returned, AddressOf OnLoanReturned
    End Sub

    Private Sub OnLoanReturned(sender As Object, e As LoanEventArgs)
        Console.WriteLine("reader notified: " & e.Borrower)
    End Sub
End Class

Public Class CatalogException
    Inherits Exception

    Public Sub New(message As String)
        MyBase.New(message)
    End Sub

    Public Sub New(message As String, innerException As Exception)
        MyBase.New(message, innerException)
    End Sub
End Class

Public Class DuplicateIsbnException
    Inherits CatalogException

    Public ReadOnly Property Isbn As String

    Public Sub New(isbn As String)
        MyBase.New("Duplicate ISBN " & isbn)
        Me.Isbn = isbn
    End Sub
End Class

Module Program
    Sub Main()
        Dim loan As New AuditedLoan("Ana")
        Dim reader As New Reader()
        reader.Attach(loan)
        loan.Return()

        Try
            Throw New DuplicateIsbnException("9780132350884")
        Catch ex As DuplicateIsbnException When ex.Isbn.StartsWith("978", StringComparison.Ordinal)
            Console.WriteLine("specific: " & ex.Message)
        Catch ex As CatalogException
            Console.WriteLine("general: " & ex.Message)
        End Try
    End Sub
End Module
audit: Ana returned the item
reader notified: Ana
specific: Duplicate ISBN 9780132350884
  • AuditedLoan overrides OnReturned, does its own work and calls MyBase.OnReturned(e). Forget the call and no handler is ever notified, and the compiler will not say so.

  • The exception hierarchy at the bottom of the program shows the same shape: DuplicateIsbnException derives from CatalogException, which derives from Exception and forwards the message and the inner exception to MyBase.New. The Catch ... When filter and the order of the Catch blocks (most derived first) are covered in Structured Exception Handling. Derive custom exceptions from Exception, not from ApplicationException.

  • Handles MyBase.Event and WithEvents fields in a derived class are described in Events and Handlers.

Interfaces and inheritance

A class can implement an interface and inherit from a class; interface members implemented in a base class are inherited by the derived classes. When the member is Overridable, the interface reaches the override. Shadows is not an override, and the interface keeps pointing to the base member unless the derived class repeats Implements.

Option Strict On

Public Interface IDescribable
    Function Describe() As String
End Interface

Public Class Item
    Implements IDescribable

    Public Overridable Function Describe() As String Implements IDescribable.Describe
        Return "Item"
    End Function
End Class

Public Class Book
    Inherits Item

    Public Overrides Function Describe() As String
        Return "Book"
    End Function
End Class

' Shadows without re-implementing: the interface still points at Item.Describe.
Public Class Atlas
    Inherits Item

    Public Shadows Function Describe() As String
        Return "Atlas"
    End Function
End Class

' Re-implementing the interface rebinds it to the new member.
Public Class Map
    Inherits Item
    Implements IDescribable

    Public Shadows Function Describe() As String Implements IDescribable.Describe
        Return "Map"
    End Function
End Class

Module Program
    Sub Main()
        Dim book As New Book()
        Dim atlas As New Atlas()
        Dim map As New Map()

        For Each d As IDescribable In New IDescribable() {book, atlas, map}
            Console.WriteLine("via interface: " & d.Describe())
        Next
        Console.WriteLine("via Item   : " & CType(atlas, Item).Describe() & ", " & CType(map, Item).Describe())
        Console.WriteLine("via Atlas  : " & atlas.Describe())
        Console.WriteLine("via Map    : " & map.Describe())
    End Sub
End Module
via interface: Book
via interface: Item
via interface: Map
via Item   : Item, Item
via Atlas  : Atlas
via Map    : Map
  • Book overrides Describe: the interface reaches Book.Describe (Book).

  • Atlas shadows it: through the interface, Item.Describe still runs (Item); only through an Atlas variable does the new method run.

  • Map repeats Implements IDescribable and names the interface member on its own method (interface re-implementation), so the interface reaches Map.Describe (Map), while a plain Item variable still reaches Item.Describe.

An abstract class can implement an interface partially: it redeclares the members it does not want to implement as MustOverride, keeping the Implements clause on them, and the derived classes override them.

Option Strict On

Public Interface IDescribable
    Function Describe() As String
End Interface

' An abstract class may implement an interface member by redeclaring it MustOverride.
Public MustInherit Class CatalogItem
    Implements IDescribable

    Public MustOverride Function Describe() As String Implements IDescribable.Describe
End Class

Public Class Book
    Inherits CatalogItem

    Public Overrides Function Describe() As String
        Return "Book"
    End Function
End Class

Module Program
    Sub Main()
        Dim d As IDescribable = New Book()
        Console.WriteLine(d.Describe())
    End Sub
End Module
Book

Source: MustOverride

Interfaces, the Implements clause, default interface members and multiple interfaces are the subject of Interfaces.

Members every object inherits

Object contributes ToString, Equals, GetHashCode, GetType, the protected MemberwiseClone and Finalize, and the static Equals and ReferenceEquals. The ones worth overriding in a domain class are described here only as they relate to inheritance; the details are in other pages.

  • ToString is Overridable; override it with Public Overrides Function ToString() As String. Seal it with NotOverridable when subclasses must not change the text, as the template-method example does.

  • Equals/GetHashCode must be overridden together and be consistent across the hierarchy; a derived class that adds fields to the equality must be careful not to break symmetry with its base class (see Operator Overloading and Equality).

  • Finalize is Protected Overrides in Visual Basic and almost never needed; implement IDisposable instead (see Object Lifetime and Disposal).

  • MemberwiseClone is Protected, so only the class itself (or a derived class) can call it, from a method such as ShallowCopy. It copies the fields bit by bit: value-type fields are duplicated, but reference-type fields are shared between the original and the copy.

Option Strict On

Public Class Author
    Public Property Name As String = ""
End Class

Public Class Shelf2
    Public Property Label As String = ""
    Public Property Owner As New Author()
    Public Property Tags As New List(Of String)

    Public Function ShallowCopy() As Shelf2
        Return DirectCast(MemberwiseClone(), Shelf2)    ' Protected member of Object
    End Function
End Class

Module Program
    Sub Main()
        Dim a As New Shelf2 With {.Label = "Fiction"}
        a.Owner.Name = "Ana"
        a.Tags.Add("novel")

        Dim b As Shelf2 = a.ShallowCopy()
        b.Label = "Poetry"
        b.Owner.Name = "Ben"
        b.Tags.Add("verse")

        Console.WriteLine(a.Label & " / " & a.Owner.Name & " / " & String.Join(",", a.Tags))
        Console.WriteLine(b.Label & " / " & b.Owner.Name & " / " & String.Join(",", b.Tags))
        Console.WriteLine(Object.ReferenceEquals(a.Owner, b.Owner))
    End Sub
End Module
Fiction / Ben / novel,verse
Poetry / Ben / novel,verse
True

Changing b.Label did not change a.Label (the field of the copy was reassigned), but b.Owner.Name and b.Tags changed the objects that both copies refer to, which ReferenceEquals confirms. If a deep copy is needed, write it by hand or use a copy constructor; a clone method on a hierarchy needs an override on every class, which is one more reason to prefer copy constructors.

Designing hierarchies

  • Is-a, not has-a. A Loan is not a CatalogItem; it has one. Inheritance couples a class to the internals of its base class. Prefer composition (a field that holds another object, and delegates to it) and interfaces unless the derived class really is a specialisation that can stand in for the base in every context (the Liskov substitution principle).

  • Design for inheritance or prohibit it. A class that is meant to be extended documents which Overridable members call which others, and which are safe to override. A class that is not meant to be extended is NotInheritable. Every Overridable member is a promise that the next version of the base class has to keep, so add extension points deliberately, as the framework design guidelines advise.

  • Fragile base class. Adding or changing an Overridable member in the base class can break derived classes in other assemblies even without changing the signature, for example by calling it from a different place. Another reason to make the template method the only extension mechanism.

  • Keep hierarchies shallow. Two or three levels are common; five are a design question. Abstract base classes are most useful when they hold shared state and algorithms (the LoanPolicy example), interfaces when only a contract is shared.

  • Do not use inheritance for reuse alone. A Stack that inherits from List exposes insert and remove at any position. Wrap, do not inherit.

  • Inheriting from framework classes is the common case: Exception, EventArgs, Collection(Of T) (override InsertItem, SetItem, RemoveItem), Stream, DbContext, Form and UserControl. Read the documentation of the base for the members intended to be overridden, and call the MyBase version unless it says otherwise.

  • Where a hierarchy would need more than one base class, use interfaces plus composition. Visual Basic has no multiple class inheritance, and no mixins or traits.

Differences from C# at a glance

Topic Visual Basic C#

Declare inheritance

Inherits Base as the first statement of the body

class Derived : Base

Make virtual / override

Overridable / Overrides

virtual / override

Abstract / sealed

MustInherit, MustOverride / NotInheritable, NotOverridable

abstract / sealed

Hide a member

Shadows (hides by name, all overloads)

new (hides by signature)

Forgetting the keyword

warning BC40003 / BC40005

warning CS0108 / CS0114

Call the base implementation

MyBase.Name()

base.Name()

Non-virtual call in the same class

MyClass.Name()

not possible

Constructor chaining

MyBase.New(...) / Me.New(...) as the first statement

: base(...) / : this(...) after the signature

Derived field initializers

run after the base constructor

run before the base constructor

Covariant return types

not supported (BC30437)

supported since C# 9

Type patterns (x is Book b)

not supported; TypeOf + DirectCast/TryCast

supported

Default member access

Public

private

Default constructor of an abstract class

Protected

protected

Intersection of Protected and Friend

Private Protected (VB 15.5)

private protected

Class inheritable by default

yes

yes

Interface member in base, re-implementation

repeat Implements on the derived class

repeat the interface in the derived class

The same hierarchy in C# has the following shape, which is what a C# consumer of a Visual Basic class library sees:

public abstract class CatalogItem
{
    protected CatalogItem(string title, int year) { Title = title; Year = year; }
    public string Title { get; set; }
    public int Year { get; set; }
    public abstract string Kind { get; }
    public virtual string Describe() => $"{Kind}: {Title} ({Year})";
}

public class Book : CatalogItem
{
    public Book(string title, int year, params string[] authors) : base(title, year) { Authors.AddRange(authors); }
    public List<string> Authors { get; } = new();
    public override string Kind => "Book";
    public override string Describe() => base.Describe() + " by " + string.Join(", ", Authors);
}

More on the C# side is in Inheritance and Polymorphism in the C# reference. Cross-language use of a Visual Basic hierarchy is covered in Interop with C# and Other .NET Languages.

Quick error reference

The messages that this page has shown, collected by what to do. Each was produced by the compiler on this page’s examples.

Code Cause Fix

BC30610

concrete class does not override an inherited MustOverride member

implement it, or declare the class MustInherit

BC31411

MustOverride member in a class that is not MustInherit

add MustInherit

BC30569

New on a MustInherit class

create a derived class

BC31086

Overrides of a member that is not Overridable

add Overridable to the base member, or stop overriding

BC30266

different access level in the override

use the same access

BC30437

different return type in the override

return the base type

BC30362

different ReadOnly/WriteOnly

declare the same accessors

BC30307

different default value of an optional parameter

same default, or no Optional

BC31088

NotOverridable without Overrides

add Overrides, or drop it

BC30607

NotOverridable in a NotInheritable class

remove one

BC30299

inheriting from a NotInheritable class

remove NotInheritable, or compose

BC30121, BC30258, BC30628

several bases, a non-class base, Inherits in a structure

one class base; Implements for interfaces

BC30501, BC31408

Shared or Private combined with Overridable

remove one of them

BC30387, BC30148, BC30282

constructor call problems

call MyBase.New(...) first

BC30390, BC30399, BC30614, BC30029

Protected access, MyBase or MyClass on an abstract member, RaiseEvent of a base event

access through the derived type; call an overridden member; use an OnXxx method

BC32027, BC30205

MyBase used as a value; a type pattern

a member access; a separate cast

BC40003, BC40005, BC42025

warnings: hiding without a keyword; an Overridable member hidden; Shared through an instance

add Overloads, Overrides or Shadows; qualify with the type

The numbers are those of the Visual Basic compiler messages. The individual error pages are reachable from the Microsoft Learn index of Visual Basic compiler messages.

Older material you may meet

  • Visual Basic 6 and VBA have no implementation inheritance. Tutorials for them simulate it with Implements and delegation (a field that holds an inner object, and wrapper methods); in Visual Basic .NET use Inherits where the relation is is-a, and keep the delegation where it is has-a.

  • Class_Initialize and Class_Terminate correspond to Sub New and, rarely, Finalize or IDisposable; the base class constructor must be called with MyBase.New, which VB6 had no concept of.

  • Early Visual Basic .NET texts (2002 and 2003) derive exceptions from ApplicationException and omit Option Strict On; the examples here use neither.

  • Texts that explain polymorphism with a hierarchy of shapes or animals and a cascade of TypeOf ... Is tests are showing the pattern that this page recommends replacing with an overridable member.

  • Code written for Option Strict Off can call members on an Object variable and appear to be polymorphic without a common base; that is late binding (see Early and Late Binding).