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
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
-
Inheritsmust 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 IDisposableis BC30258 ("Classes can inherit only from other classes"): to implement an interface useImplements(see Interfaces). A class cannot inherit from a class declaredNotInheritable(BC30299), which includesStringand every structure. -
A class that has no
Inheritsstatement inherits fromSystem.Object. Every class, structure, enumeration, delegate and array is therefore also anObject. -
Structures cannot use
Inherits(BC30628): they derive fromSystem.ValueTypeimplicitly, and enumerations fromSystem.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
NotInheritableto 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.
-
Object.ToStringreturns the full type name (Shelf.Plain) unless you override it, which is why overridingToStringon every domain class is such a common first exercise. -
Object.Equalscompares references for classes (Falsefor two different instances,Truefor the same one). Value equality has to be written (see Operator Overloading and Equality). -
A
Moduleis compiled to a sealed class; a structure is aValueType; an enumeration is anEnum; an array type derives fromSystem.Array, and a delegate type fromMulticastDelegate. -
An interface type has no base type (
BaseTypeisNothing), although every object that implements it is anObject.
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
Source: Inherits Statement
What the example shows.
-
Protected Sub Newin an abstract class: only derived classes can call it (throughMyBase.New), so the constructor does not appear in the public surface of the class. When aMustInheritclass declares no constructor, the compiler generates aProtectedone; a concrete class gets aPublicone (verified by reflection in Constructors in a hierarchy). -
MustOverride ReadOnly Property Kindhas no body and noEnd Property.Bookoverrides it with an explicitGetblock,Magazinewith a read-only auto-property that has an initializer (read-only auto-properties with initializers arrived in Visual Basic 14; overriding them is allowed). -
DescribeisOverridable;Book.DescribecallsMyBase.Describe()to extend the base behaviour instead of replacing it, andMagazineinherits the default. -
Dim first As CatalogItem = items(0)holds aBook.first.GetType()andTypeName(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 |
|---|---|---|
|
Cannot be instantiated; may contain implemented members, fields and constructors, and |
|
|
No body, implicitly |
|
|
Cannot be the base of another class (BC30299) |
|
|
Only on an |
|
-
A concrete class that inherits a
MustOverridemember without overriding it is rejected with BC30610 (the message lists the missing members). Declaring the classMustInherittoo postpones the obligation to its own derived classes. -
MyBase.KindorMyClass.Id()on aMustOverridemember is BC30399 or BC30614: there is no implementation to call. -
NotOverridablein aNotInheritableclass 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 classequivalent other than aModule; aNotInheritableclass with onlySharedmembers and aPrivate Sub Newis 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.
Source: MustInherit
NotInheritable
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 |
|---|---|---|
|
The member can be overridden; calls through a base-typed reference reach the most derived override |
|
|
Replaces an inherited |
|
|
Seals an overriding member (only valid together with |
|
|
Declares the member without a body in a |
|
|
Hides an inherited member of any kind by name; no run-time dispatch |
|
|
Adds a member with the same name and a different signature next to the inherited ones |
(implicit in C#) |
|
Non-virtual call of the base class implementation |
|
|
Non-virtual call of the member of the current class, ignoring overrides |
no equivalent |
|
The current object |
|
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 |
|
Access level |
BC30266 |
|
|
BC30362 |
adding a |
Default values of |
BC30307 |
see below |
Base member must be overridable |
BC31086 |
|
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
-
WeekdayPolicyoverrides only the optional step and inherits the 14 days;ShortPolicyoverrides the essential step and callsMyBase.BaseDays()(the 14 days ofStandardPolicy) to halve it.\is the integer division operator.ShortPolicyinherits the weekday adjustment of its base class: 2026-10-24 is a Saturday, so the due date moves to Monday. -
DueDateis not overridable, so no subclass can change the algorithm.ToStringis sealed withNotOverridable Overrides, an override of a member that was declared inObject, so no subclass can change the text. -
The 2026-10-17 borrow date is a Saturday. For
StandardPolicythe 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
Source: MethodBase.IsFinal
| Visual Basic | What the output shows |
|---|---|
|
|
|
|
|
|
|
|
plain method |
|
|
|
|
|
|
a plain non-virtual method ( |
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.
-
Overridesreplaces an inherited member: the slot is shared, and the call is resolved at run time from the real class of the object. -
Overloadsadds a member with a different signature, and keeps every inherited member of that name visible. -
Shadowsintroduces 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
Source: Shadowing in Visual Basic
Reading the output line by line.
-
Labelis overridden: the base-typed and the derived-typed variable print the same text. This is polymorphism. -
Fixedis shadowed. The same object printsCatalogItem.Fixedthrough the base variable andBook.Fixedthrough 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. -
Shelfshadows an overridable method. It compiles without a warning becauseShadowsis explicit, and the base variable still reachesCatalogItem.Shelf. This is almost never what the author wanted; compare the warning BC40005 below. -
Show(Double)inBookis declaredOverloads, so all three overloads are visible on aBook:b.Show(1)picksShow(Integer)from the base class. -
Atlas.Show(Double)is declaredShadowsand therefore hides bothShowoverloads ofCatalogItem:a.Show(5)converts the argument toDoubleand callsAtlas.Show(Double), neverShow(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
Overridablebase method, and noOverridesorShadows. Nearly always a forgottenOverrides. The method is not an override. -
BC40003: a method that hides inherited overloadable members without
OverloadsorShadows. Either addOverloadsto keep the base overloads visible, orShadowsif hiding is intended.Fixedis reported here too, although it has the same signature, because the base method is not overridable. -
BC42025:
b.Create()uses an instance to reach aSharedmember, and the instance expression is not evaluated. CallBook.Create()orCatalogItem.Create(). The program also shows thatSharedmembers are inherited:Book.Create()andBook.Counterreach the members ofCatalogItem, andBook.Counter = 3assigns the one shared field. -
Treat these warnings as errors (
TreatWarningsAsErrorsor an.editorconfigseverity); 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
-
Meis the current object, of its run-time class. A callMe.Name()of anOverridablemember reaches the most derived override (virtual dispatch). -
MyBaseaccesses 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.MyBaseis not a value: it must be followed by a dot and a member (Return MyBaseis BC32027, see the error listing below). -
MyClasscalls a member of the class where the call is written, ignoring overrides in derived classes: a non-virtual call of anOverridablemethod. 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(...)andMyBase.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
Source: Me, My, MyBase, and MyClass
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(...)(orMe.New(...)/MyClass.New(...)), the compiler insertsMyBase.New(). When the base class has no accessible parameterless constructor, the compiler refuses: BC30387 for a class without anySub 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(); aMustInheritclass gets aProtectedone. The code below confirms it by reflection, together with two other defaults that differ from C#: a method without an access modifier isPublic, a field declared withDimisPrivate, and a top-level type without a modifier isFriend.
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.Newcall.
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)
Source: Instance constructors (C#)
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 |
E2 |
BC30399 |
|
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 |
E5 |
BC30282 |
a second constructor call, or one that is not first |
E6 |
BC30029 |
|
E7 |
BC30614 |
|
E8 |
BC30569 |
|
E9 |
BC36756 |
|
E10 |
BC30512 |
an implicit narrowing conversion under |
E11 |
BC31430 |
|
E12 |
BC30390 |
a |
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 |
|---|---|---|
|
|
|
|
|
|
|
Exact class match; a derived class is not a match |
|
|
Same as |
|
|
Reference conversion with a run-time check, no conversion functions |
|
|
Like |
|
|
Casts, plus every user-defined or built-in conversion ( |
|
|
Reference identity (the same object); does not test a type |
|
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
Source: DirectCast Operator
TryCast Operator
-
The
Textbooktest comes first inSummarize:TypeOf item Is Bookis alsoTruefor aTextbook, so the most specific class must be tested first.GetType(Book)equality isFalsefor the same object. Pick the operator that matches the intent:TypeOffor "is usable as",GetTypeequality for "is exactly". -
TypeOf Nothing Is BookisFalseandTryCastofNothingisNothing; noNullReferenceException. -
The message of the
InvalidCastExceptionnames the full class names (Shelf.Magazine,Shelf.Book). The root namespace is part of the name. -
Select Case TruewithCase TypeOf t Is Magazineis the nearest thing to a type switch: the cases are tested in order, so again, derived types before base types. -
DirectCastafter aTypeOftest is correct but tests twice;TryCastfollowed by anIs Nothingcheck does both in one step and is the idiom when the cast result is needed.TypeOfalone is the idiom when only the answer is needed. -
The need for many
TypeOftests on the same hierarchy is usually a smell: add an overridable member to the hierarchy (theDescribeof 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 toIEnumerable(Of CatalogItem): the interface is declaredOut T(covariant), because items only come out.Action(Of CatalogItem)converts toAction(Of Book): the delegate is declaredIn T(contravariant), because the argument goes in. -
List(Of Book)does not convert toList(Of CatalogItem)(BC36756): a class cannot be variant, because the list would allowAdd(New Magazine ...)through the item-typed view. The message suggestsIEnumerable(Of CatalogItem). Copying (New List(Of CatalogItem)(books)) makes a separate list. -
Arrays are covariant, which compiles and is unsafe: storing a
Magazinethrough aCatalogItem()that is really aBook()throwsArrayTypeMismatchExceptionat run time. PreferIReadOnlyList(Of T)/IEnumerable(Of T)for the parameters of methods that only read. -
Your own variant interfaces use
Out/Inin the declaration (see Generics).
Access modifiers and inheritance
| Modifier | Visible to |
|---|---|
|
every caller |
|
the declaring class and its derived classes, in any assembly; through a derived-typed reference only |
|
every type in the same assembly (C# |
|
the union: derived classes in any assembly, plus every type in the same assembly (C# |
|
the intersection: derived classes in the same assembly only (C# |
|
the declaring type, including its nested types |
-
A member without an access modifier is
Public(members of types), aDimfield isPrivate, and a top-level type isFriend. C# defaults toprivateandinternal. -
An override must have the same access as the member it overrides (BC30266).
-
The
Protectedrule is subtle: insideBook,Me.Secret(),MyBase.Secret()andotherBook.Secret()compile, butotherItem.Secret()withotherItem As CatalogItemdoes not (BC30390, E1 in the constructor-errors listing), because the other object may be aMagazine, a sibling class whose protected membersBookhas 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
Source: RaiseEvent Statement
-
AuditedLoanoverridesOnReturned, does its own work and callsMyBase.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:
DuplicateIsbnExceptionderives fromCatalogException, which derives fromExceptionand forwards the message and the inner exception toMyBase.New. TheCatch ... Whenfilter and the order of theCatchblocks (most derived first) are covered in Structured Exception Handling. Derive custom exceptions fromException, not fromApplicationException. -
Handles MyBase.EventandWithEventsfields 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
Source: Implements Statement
-
BookoverridesDescribe: the interface reachesBook.Describe(Book). -
Atlasshadows it: through the interface,Item.Describestill runs (Item); only through anAtlasvariable does the new method run. -
MaprepeatsImplements IDescribableand names the interface member on its own method (interface re-implementation), so the interface reachesMap.Describe(Map), while a plainItemvariable still reachesItem.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.
-
ToStringisOverridable; override it withPublic Overrides Function ToString() As String. Seal it withNotOverridablewhen subclasses must not change the text, as the template-method example does. -
Equals/GetHashCodemust 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). -
FinalizeisProtected Overridesin Visual Basic and almost never needed; implementIDisposableinstead (see Object Lifetime and Disposal). -
MemberwiseCloneisProtected, so only the class itself (or a derived class) can call it, from a method such asShallowCopy. 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
Source: Object.MemberwiseClone
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
Loanis not aCatalogItem; 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
Overridablemembers call which others, and which are safe to override. A class that is not meant to be extended isNotInheritable. EveryOverridablemember 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
Overridablemember 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
LoanPolicyexample), interfaces when only a contract is shared. -
Do not use inheritance for reuse alone. A
Stackthat inherits fromListexposes insert and remove at any position. Wrap, do not inherit. -
Inheriting from framework classes is the common case:
Exception,EventArgs,Collection(Of T)(overrideInsertItem,SetItem,RemoveItem),Stream,DbContext,FormandUserControl. Read the documentation of the base for the members intended to be overridden, and call theMyBaseversion 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 |
|
|
Make virtual / override |
|
|
Abstract / sealed |
|
|
Hide a member |
|
|
Forgetting the keyword |
warning BC40003 / BC40005 |
warning CS0108 / CS0114 |
Call the base implementation |
|
|
Non-virtual call in the same class |
|
not possible |
Constructor chaining |
|
|
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 ( |
not supported; |
supported |
Default member access |
|
|
Default constructor of an abstract class |
|
|
Intersection of |
|
|
Class inheritable by default |
yes |
yes |
Interface member in base, re-implementation |
repeat |
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 |
implement it, or declare the class |
BC31411 |
|
add |
BC30569 |
|
create a derived class |
BC31086 |
|
add |
BC30266 |
different access level in the override |
use the same access |
BC30437 |
different return type in the override |
return the base type |
BC30362 |
different |
declare the same accessors |
BC30307 |
different default value of an optional parameter |
same default, or no |
BC31088 |
|
add |
BC30607 |
|
remove one |
BC30299 |
inheriting from a |
remove |
BC30121, BC30258, BC30628 |
several bases, a non-class base, |
one class base; |
BC30501, BC31408 |
|
remove one of them |
BC30387, BC30148, BC30282 |
constructor call problems |
call |
BC30390, BC30399, BC30614, BC30029 |
|
access through the derived type; call an overridden member; use an |
BC32027, BC30205 |
|
a member access; a separate cast |
BC40003, BC40005, BC42025 |
warnings: hiding without a keyword; an |
add |
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
Implementsand delegation (a field that holds an inner object, and wrapper methods); in Visual Basic .NET useInheritswhere the relation is is-a, and keep the delegation where it is has-a. -
Class_InitializeandClass_Terminatecorrespond toSub Newand, rarely,FinalizeorIDisposable; the base class constructor must be called withMyBase.New, which VB6 had no concept of. -
Early Visual Basic .NET texts (2002 and 2003) derive exceptions from
ApplicationExceptionand omitOption Strict On; the examples here use neither. -
Texts that explain polymorphism with a hierarchy of shapes or animals and a cascade of
TypeOf ... Istests are showing the pattern that this page recommends replacing with an overridable member. -
Code written for
Option Strict Offcan call members on anObjectvariable and appear to be polymorphic without a common base; that is late binding (see Early and Late Binding).
References
-
Modifiers index: MustInherit, NotInheritable, Overridable, Overrides, NotOverridable, MustOverride, Shadows and Overloads
-
Protected, Friend, Protected Friend, Private Protected and Shared
-
Shadowing in Visual Basic and Differences Between Shadowing and Overriding
-
TypeOf Operator, DirectCast Operator, TryCast Operator and CType Function
-
Class Statement, Implements Statement and RaiseEvent Statement
-
Designing for Extensibility, Protected Members and Base Classes for Implementing Abstractions (.NET Framework Design Guidelines)
-
Object Class, Object.MemberwiseClone, Type.BaseType, Type.IsInstanceOfType, MethodBase.IsFinal and MethodInfo.GetBaseDefinition