Classes and Objects
|
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, was built with warnings treated as errors (unless the text
says otherwise) and was run in the .NET 10 SDK container (Linux); the output shown is real output. The examples that are
meant to fail say so, and show the compiler messages that the SDK printed. The projects use the root namespace Shelf,
which is why the runtime messages below mention Shelf.Magazine and not just Magazine. The project-level imports that the
SDK adds (Microsoft.VisualBasic, System, System.Collections.Generic, System.Linq and others) are why Console,
List(Of T) and LINQ need no Imports line.
A class is the basic unit of object-oriented code in Visual Basic: a reference type that bundles data (fields), behaviour
(methods) and the rules for creating and exposing them. Almost everything you write for a real application is a class:
the shelf library catalogue used throughout this reference is built from Book, Magazine, Author, Loan and
Catalog. This page covers the class itself, how an object comes into existence, how its members are declared and how
visible each one is, and how two references to objects compare. Properties and default properties are on
Properties and Default Properties,
Shared members and Module on
Modules and Shared Members, value types on
Structures and Value Types, inheritance on
Inheritance and Polymorphism, interfaces on
Interfaces, events on
Events and Handlers, equality operators on
Operator Overloading and Equality and
disposal on Object Lifetime and Disposal. If you
come from C#, the same topic is on
Classes and Objects in C#; the differences are collected in a
table near the end of this page.
| Kind of type | Declared with | Main characteristic |
|---|---|---|
Class |
|
reference type; created with |
Structure |
|
value type, copied on assignment (see Structures and Value Types) |
Module |
|
a type whose members are all |
Interface |
|
a contract with no state (see Interfaces) |
Enumeration |
|
a named set of integral constants (see Enumerations) |
Delegate |
|
a type for method references (see Delegates and Lambda Expressions) |
Declaring a class
[<attributes>] [accessmodifier] [Shadows] [MustInherit | NotInheritable] [Partial] Class Name [(Of typeparameters)]
[Inherits BaseClass]
[Implements Interface1, Interface2]
members
End Class
-
The members are declarations between
ClassandEnd Class: fields,Const, methods (SubandFunction), properties, events, operators, constructors, nested types, andDeclarestatements. Executable statements are only allowed inside procedures, never directly in the class body. -
Classhas no mandatoryPublic. A top-level class without an access modifier isFriend, visible inside its assembly only (the reflection example in the access modifiers section prints it). WritePublicfor every type that is part of a library’s API. -
MustInheritmakes the class abstract (it cannot be created withNew, BC30569),NotInheritablemakes it sealed. They are mutually exclusive. Both are developed on Inheritance and Polymorphism. -
Shadowson a nested class hides a base-class member of the same name. -
Partialsplits the declaration over several files (see below), andOfadds type parameters (see Generics). -
A file may contain any number of classes, but the usual convention is one type per file named after the type. The layout of a source file is on Program Structure and Code Conventions.
-
Names are not case sensitive.
Book,BOOKandbookare the same identifier, so a local variable calledbookof typeBookis legal and common (Dim book As Book), but a class and a method of the same spelling in one scope are not.
The first complete example shows the vocabulary used in the rest of the page: private fields, a constructor, an instance
method that uses the fields, a Shared field that belongs to the class and an overridden ToString.
Option Strict On
Public Class Loan
Private ReadOnly _title As String
Private _renewals As Integer
Public Shared ReadOnly MaxRenewals As Integer = 2
Public Shared Created As Integer
Public Sub New(title As String)
_title = title
Created += 1
End Sub
Public Function Renew() As Boolean
If _renewals >= MaxRenewals Then
Return False
End If
_renewals += 1
Return True
End Function
Public Overrides Function ToString() As String
Return _title & " (renewed " & _renewals & " times)"
End Function
End Class
Module Program
Sub Main()
Dim first As New Loan("Refactoring")
Dim second As Loan = New Loan("Domain-Driven Design")
Console.WriteLine(first.Renew())
Console.WriteLine(first.Renew())
Console.WriteLine(first.Renew())
Console.WriteLine(first)
Console.WriteLine(second)
Console.WriteLine("Loans created: " & Loan.Created)
End Sub
End Module
True
True
False
Refactoring (renewed 2 times)
Domain-Driven Design (renewed 0 times)
Loans created: 2
Source: Class Statement
Three details to note. Dim first As New Loan("Refactoring") and Dim second As Loan = New Loan("...") do the same thing;
the first form is shorter, the second can also be written with a different type on the left, as in Dim item As CatalogItem = New Book(...). The third Renew() returns False because the limit lives in a Shared field that every instance reads. And
Console.WriteLine(first) printed the result of the overridden ToString, because WriteLine(Object) calls it.
What goes inside a class
| Member | What it is | Default accessibility (checked on this page) |
|---|---|---|
Field ( |
a variable stored in each object (or once per class if |
|
Constant ( |
a compile-time value |
|
|
a method |
|
|
a field-like member with accessors |
|
|
a notification |
|
|
an instance constructor |
|
|
the type initializer, run once before first use |
|
|
a user-defined operator |
must be written |
Nested |
a type declared in the class |
|
The defaults differ from C#, where every member is private unless stated. Always write the modifier: the defaults are a
frequent source of surprise when you read code that omits them, and a code review rule that every member has an explicit
access modifier costs nothing. The example in the access modifiers section prints these defaults by reflection.
Fields
A field is a variable declared in the class body. Dim in a class means Private, so Dim Count As Integer is a private
field, while Public Count As Integer is a public one. Prefer Private fields, exposed through properties when other code
needs them: a public field cannot later change into a property without recompiling every caller, cannot be used in data
binding, and cannot carry validation.
-
The usual naming convention is a leading underscore (
_title), which also avoids the clash with a parameter of the same name described under "Case-insensitivity" below. -
ReadOnlyfields may be assigned only in their declaration or in a constructor of the same class (BC30064 otherwise).Shared ReadOnlyfields may be assigned in their declaration or inShared Sub New. -
Constfields are compile-time constants, implicitlyShared, and only of an intrinsic type (numbers,Boolean,Char,Date,String) or anEnum. UseShared ReadOnlyfor anything else, such as an object or a computed value. -
WithEventsfields make event wiring withHandlespossible; see Events and Handlers. -
A field may be declared with an initializer,
Private _count As Integer = 5, or withAs New,Private ReadOnly _log As New List(Of String). The initializers of instance fields can useMeand the fields declared before them (unlike C#, where an instance initializer cannot refer to another instance member).
Option Strict On
Public Class Order
Private ReadOnly _quantity As Integer = 3
Private ReadOnly _price As Decimal = 2.5D
Private ReadOnly _total As Decimal = _quantity * _price ' an instance initializer may use earlier instance fields
Private ReadOnly _created As Date = Date.Today.Date
Public ReadOnly Property Total As Decimal
Get
Return _total
End Get
End Property
End Class
Module Program
Sub Main()
Console.WriteLine(New Order().Total)
End Sub
End Module
7.5
Source: ReadOnly
When the initializers run
The order in which a new object is built matters when constructors call overridable methods. The rule in Visual Basic is:
-
memory for the object is allocated and every field has its default value (
0,False,Nothing); -
the constructor starts. If its first statement calls
MyBase.New(...), the base constructor runs first, to the end; if it callsMe.New(...), that other constructor of the same class runs first and the initializers are not repeated; -
immediately after the base constructor has returned, the instance field initializers of this class run in the order they are written;
-
then the rest of the constructor body runs.
C# differs: field initializers run before the base constructor. The consequence in Visual Basic is that a base constructor
that calls an Overridable method sees the derived class’s fields before their initializers ran:
Option Strict On
Public Class Base
Public Sub New()
Console.WriteLine("Base.New starts")
Console.WriteLine(" Describe() says: " & Describe())
Console.WriteLine("Base.New ends")
End Sub
Public Overridable Function Describe() As String
Return "base"
End Function
End Class
Public Class Derived
Inherits Base
Private ReadOnly _label As String = "initialized"
Private _count As Integer = 5
Private ReadOnly _upper As String = _label.ToUpperInvariant()
Public Sub New()
MyBase.New()
Console.WriteLine("Derived.New body: " & Describe())
End Sub
Public Overrides Function Describe() As String
Return "label=" & If(_label, "<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 starts
Describe() says: label=<Nothing>, count=0
Base.New ends
Derived.New body: label=initialized, count=5
label=initialized, count=5
While Base.New ran, _label was still Nothing and _count was 0, although both have initializers. Calling an overridable
member from a constructor is a known design problem in every .NET language; in Visual Basic it is slightly worse than in C#
because even the initializer values are missing. Keep constructors free of virtual calls, or make the member
NotOverridable/Private, or move the work into an explicit Initialize step.
Constructors
A constructor is a Sub named New. It runs once for every object created with New, after the memory has been zeroed.
[accessmodifier] Sub New([parameters])
[MyBase.New(...) | MyClass.New(...) | Me.New(...)]
statements
End Sub
-
A class that declares no
Sub Newreceives a default public parameterless constructor from the compiler. As soon as you declare any constructor, the default one disappears:New Reader()then fails with BC30455 when onlySub New(name As String)exists. -
Constructors can be overloaded like any method. They may use
Optionalparameters and named arguments (New Reader(limit:=9)), though many designs prefer two or three explicit overloads so the intent is clear in the call. -
To reuse one constructor from another, call
Me.New(...)as the first statement.Me.Newanywhere else is BC30282. A cycle of constructor calls is BC30298. -
The constructor of the base class is called with
MyBase.New(...), also as the first statement. If you omit the call, the compiler insertsMyBase.New()and that must exist and be accessible: otherwise BC30387 (when the derived class declares noSub Newat all) or BC30148 (when it declares one without the call). A constructor cannot have both aMe.Newand aMyBase.Newcall. -
A constructor does not return a value;
Returntakes no expression there (BC30647). -
A
Private Sub Newprevents creating objects from outside, which is how factory methods, singletons and classes of onlySharedmembers are written. -
Shared Sub Newis the type initializer. It has no parameters and no access modifier, runs once, before the first instance is created or the firstSharedmember is read, and runs in a thread-safe way. If it throws, the type becomes unusable: every later access throwsTypeInitializationException(see "Instance and Shared members").
The next program prints the order for an object built through a chain of three constructors and shows the type initializer running once:
Option Strict On
Public Class Reader
Private ReadOnly _log As New List(Of String)
Private ReadOnly _id As Integer = Seed("field initializer")
Public ReadOnly Property Name As String
Public ReadOnly Property Limit As Integer
Shared Sub New()
Console.WriteLine("Shared Sub New runs once, before first use")
End Sub
Public Sub New()
Me.New("anonymous")
Console.WriteLine("New(): body, after New(String)")
End Sub
Public Sub New(name As String)
Me.New(name, 3)
Console.WriteLine("New(String): body, after New(String, Integer)")
End Sub
Public Sub New(name As String, limit As Integer)
Console.WriteLine("New(String, Integer): body starts, _id = " & _id)
Me.Name = name
Me.Limit = limit
End Sub
Private Shared Function Seed(origin As String) As Integer
Console.WriteLine(" initializer called from: " & origin)
Return 42
End Function
End Class
Module Program
Sub Main()
Console.WriteLine("Main starts")
Dim a As New Reader()
Console.WriteLine(a.Name & " " & a.Limit)
Console.WriteLine("---")
Dim b As New Reader("Ada", 10)
Console.WriteLine(b.Name & " " & b.Limit)
End Sub
End Module
Main starts
Shared Sub New runs once, before first use
initializer called from: field initializer
New(String, Integer): body starts, _id = 42
New(String): body, after New(String, Integer)
New(): body, after New(String)
anonymous 3
---
initializer called from: field initializer
New(String, Integer): body starts, _id = 42
Ada 10
Source: Sub Statement
Read the output from the top. Main starts appears before the Shared Sub New line: the type initializer is not run when
Main starts but just before Reader is first used. The field initializer ran exactly once for a, in the constructor
that does not delegate (New(String, Integer)), and the delegating constructors ran their bodies afterwards, in reverse
order. The second object shows that Shared Sub New did not run again.
Optional parameters, named arguments and returning Me
Option Strict On
Public Class Notifier
Private ReadOnly _watchers As New List(Of Watcher)
Public Sub Register(w As Watcher)
_watchers.Add(w)
End Sub
Public Function Count() As Integer
Return _watchers.Count
End Function
End Class
Public Class Watcher
Public Sub New(owner As Notifier)
owner.Register(Me) ' Me escapes from the constructor: the object is not finished yet
End Sub
End Class
Public Class Query
Private _text As String = ""
Private _limit As Integer = 10
Private _descending As Boolean
' Returning Me allows a fluent call chain
Public Function Containing(text As String) As Query
_text = text
Return Me
End Function
Public Function Take(limit As Integer) As Query
_limit = limit
Return Me
End Function
Public Function Descending() As Query
_descending = True
Return Me
End Function
Public Overrides Function ToString() As String
Return $"text='{_text}', limit={_limit}, descending={_descending}"
End Function
End Class
Public Class Reader
Public ReadOnly Property Name As String
Public ReadOnly Property Limit As Integer
Public Sub New(Optional name As String = "anonymous", Optional limit As Integer = 3)
Me.Name = name
Me.Limit = limit
End Sub
End Class
Module Program
Sub Main()
Dim n As New Notifier()
Dim w As New Watcher(n)
Console.WriteLine("watchers: " & n.Count())
Console.WriteLine(New Query().Containing("vb").Take(5).Descending())
Dim a As New Reader()
Dim b As New Reader(limit:=9)
Dim c As New Reader("Ada", 1)
Console.WriteLine(a.Name & a.Limit & " " & b.Name & b.Limit & " " & c.Name & c.Limit)
End Sub
End Module
watchers: 1
text='vb', limit=5, descending=True
anonymous3 anonymous9 Ada1
Source: Optional Parameters
The Watcher constructor registers Me with another object before it finishes; the notifier now holds a reference to an
object that is not completely built. It is legal and sometimes needed (for example in event wiring), but it is the second
reason, with virtual calls, to keep constructors short. The Query class shows the other use of Me as a value: a method
that returns Me allows a fluent chain.
Private constructors and factories
A private constructor and a Shared factory method control how objects are created: they can validate, cache, or choose
among subclasses. NotInheritable closes the class so that no derived class can bypass the factory. A class that only
groups Shared functions can use the same two tools, although a Module is simpler (see
Modules and Shared Members).
Option Strict On
' A class that is only ever created through a Shared factory method
Public NotInheritable Class Isbn
Private Shared ReadOnly Known As New Dictionary(Of String, Isbn)
Public ReadOnly Property Value As String
Private Sub New(value As String)
Me.Value = value
End Sub
Public Shared Function Create(value As String) As Isbn
Dim existing As Isbn = Nothing
If Known.TryGetValue(value, existing) Then
Return existing
End If
Dim created As New Isbn(value)
Known.Add(value, created)
Return created
End Function
Public Overrides Function ToString() As String
Return Value
End Function
End Class
' A class that only has Shared members: NotInheritable plus a Private constructor
Public NotInheritable Class Strings
Private Sub New()
End Sub
Public Shared Function Initials(name As String) As String
Return String.Concat(name.Split(" "c).Select(Function(part) part.Substring(0, 1)))
End Function
End Class
Module Program
Sub Main()
Dim a = Isbn.Create("9780134757599")
Dim b = Isbn.Create("9780134757599")
Console.WriteLine(a.ToString() & " same instance: " & (a Is b))
Console.WriteLine(Strings.Initials("Martin Fowler"))
' Dim bad As New Isbn("1") ' BC30517: 'Sub New' is not accessible (private)
End Sub
End Module
9780134757599 same instance: True
MF
Source: NotInheritable
The singleton and factory patterns built on these ideas are on Design Patterns in Visual Basic.
Creating objects and the meaning of Nothing
New allocates an object and runs a constructor. A class-typed variable holds a reference to the object, or Nothing
(null) when it refers to none; Nothing is the default of every reference-type variable and field. The semantics of
Nothing, including its meaning for value types, are on
Nothing and Nullable Value Types.
-
Dim x As New T(args)declares and creates in one statement. With several names,Dim x, y As New T()creates one object per name;Dim p, q As Tdeclares two variables of typeT, bothNothing, with no object . -
Assigning a variable of class type copies the reference, not the object. Two variables then name the same object, and a change through one is visible through the other. Passing an object
ByValcopies the reference too: the callee can change the object but cannot make the caller’s variable refer to another object, whichByRefcan. -
A method call on
NothingthrowsNullReferenceException. The compiler warns about the obvious cases: reading a local that has not been assigned on every path gives BC42104, an error here because the project treats warnings as errors.
Option Strict On
Public Class Basket
Public Property Items As New List(Of String)
Public Property Label As String = "basket"
End Class
Module Program
Private Sub AddByVal(b As Basket)
b.Items.Add("added through the reference") ' the caller sees this
b = New Basket() ' the caller does not see this
b.Label = "new basket, local only"
End Sub
Private Sub ReplaceByRef(ByRef b As Basket)
b = New Basket With {.Label = "replaced"}
End Sub
Sub Main()
Dim first As New Basket()
Dim alias1 = first
alias1.Label = "renamed through alias"
Console.WriteLine(first.Label & " / same object: " & (first Is alias1))
AddByVal(first)
Console.WriteLine(first.Label & " / items: " & first.Items.Count)
ReplaceByRef(first)
Console.WriteLine(first.Label & " / items: " & first.Items.Count & " / alias still: " & alias1.Label)
' As New with several names creates one object per name; a separate As clause does not create any.
Dim x, y As New Basket()
Dim p, q As Basket
p = Nothing
q = x
Console.WriteLine("x Is y: " & (x Is y))
Console.WriteLine("p Is Nothing: " & (p Is Nothing) & ", q Is x: " & (q Is x))
Try
Console.WriteLine(p.Label)
Catch ex As NullReferenceException
Console.WriteLine("NullReferenceException: " & ex.Message)
End Try
End Sub
End Module
renamed through alias / same object: True
renamed through alias / items: 1
replaced / items: 0 / alias still: renamed through alias
x Is y: False
p Is Nothing: True, q Is x: True
NullReferenceException: Object reference not set to an instance of an object.
Source: New Operator
Object initializers
An object initializer creates the object and assigns accessible fields and properties in the same expression:
New Book With {.Title = "...", .Year = 2004}. They were added in Visual Basic 9.0 (Visual Studio 2008). The rules:
-
Each assignment starts with a dot and names a member that is accessible, settable and not
ReadOnly(BC30526 for a read-only property). The assignments run in the order written. -
The constructor runs first, with the arguments in parentheses if any (
New Book("title", 2004) With {.Pages = 1}), then the assignments run top to bottom. -
Initializers nest: a value can itself be
New Author With {...}. They can appear anywhere an expression is allowed: as an argument, in aReturn, in aFromcollection initializer. -
A
ReadOnlycollection property cannot be assigned in an initializer; add to it afterwards, or provide a constructor parameter. -
The matching
Withstatement (below) is a different feature, and the initializer’s list uses braces, while the statement is a block.
Option Strict On
Public Class Author
Public Property Name As String = ""
Public Property BornOn As Date?
End Class
Public Class Book
Public Property Title As String = ""
Public Property Year As Integer
Public Property Lead As Author
Public ReadOnly Property Tags As New List(Of String)
Public Property Pages As Integer = 100
End Class
Module Program
Sub Main()
Dim book As New Book With {
.Title = "Working Effectively with Legacy Code",
.Year = 2004,
.Lead = New Author With {.Name = "Michael Feathers"},
.Pages = 456
}
Console.WriteLine(book.Title & " / " & book.Lead.Name & " / " & book.Pages & " pages")
Console.WriteLine("Born: " & If(book.Lead.BornOn.HasValue, "known", "unknown"))
' Tags has no setter: fill it after creation.
book.Tags.Add("testing")
book.Tags.Add("refactoring")
Console.WriteLine(String.Join(", ", book.Tags))
' Object initializer as a method argument and with an inferred local
Dim shortBook = New Book With {.Title = "Short"}
Print(New Book With {.Title = "Inline", .Pages = 12})
Print(shortBook)
End Sub
Private Sub Print(b As Book)
Console.WriteLine(b.Title & ": " & b.Pages)
End Sub
End Module
Working Effectively with Legacy Code / Michael Feathers / 456 pages
Born: unknown
testing, refactoring
Inline: 12
Short: 100
Order of evaluation, and the With statement:
Option Strict On
Public Class Pair
Public Sub New()
Console.WriteLine("Pair.New")
End Sub
Public Property First As Integer
Public Property Second As Integer
End Class
Module Program
Private Function Trace(label As String, value As Integer) As Integer
Console.WriteLine("evaluating " & label)
Return value
End Function
Sub Main()
' The constructor runs first, then the members are assigned in the order written.
Dim p As New Pair With {.Second = Trace("second", 2), .First = Trace("first", 1)}
Console.WriteLine(p.First & "," & p.Second)
' With ... End With evaluates its expression once; a leading dot refers to it.
With p
.First = 10
.Second = .First + 1
End With
Console.WriteLine(p.First & "," & p.Second)
' A With block over a New expression.
With New Pair()
.First = 5
Console.WriteLine(.First)
End With
End Sub
End Module
Pair.New
evaluating second
evaluating first
1,2
10,11
Pair.New
5
Source: With…End With Statement
The constructor ran before either argument expression was evaluated; the initializer expressions were then evaluated in the
order they are written (second, then first), not in the order of the members' declarations. A With block evaluates its
object expression once, so With New Pair() is a convenient way to configure a temporary object. Object initializers
for anonymous types (New With {.A = 1}) are on
Tuples and Anonymous Types, and collection
initializers (From {...}) on Collections.
Me, MyBase and MyClass
| Keyword | Meaning |
|---|---|
|
the current object. An implicit receiver for unqualified instance members; explicit when a parameter or local has the same name as a member, when passing the object to another method, and when returning it. Not valid in a |
|
the base-class part of the object: calls the base-class implementation ( |
|
the current class: calls a method of this class non-virtually, ignoring overrides in derived classes. Only used with |
MyClass is a reserved word in Visual Basic. It cannot be used as the name of a class, a variable or a member
(BC30183 at the declaration and BC30180 at a use), unless you escape it with brackets, [MyClass]. The escape works for any reserved
word ([Class], [Error], [Property]), and the escaped name is the real name, so [MyClass] from VB is the same type
as MyClass from C#. Some tutorials for Visual Basic use MyClass as the sample class name; they do not compile, and the
fix is to rename the class (Sample, Book) or to escape it.
Option Strict On
Public Class Item
Public Overridable Function Label() As String
Return "Item"
End Function
Public Function ViaMe() As String
Return Me.Label()
End Function
Public Function ViaMyClass() As String
Return MyClass.Label()
End Function
Public Function Identify() As String
Return "Me is a " & Me.GetType().Name & ", MyClass is always " & GetType(Item).Name
End Function
End Class
Public Class Magazine
Inherits Item
Public Overrides Function Label() As String
Return "Magazine"
End Function
Public Function Base() As String
Return MyBase.Label()
End Function
End Class
Public Class [MyClass]
Public Function Hello() As String
Return "escaped class name"
End Function
End Class
Module Program
Sub Main()
Dim m As New Magazine()
Console.WriteLine("ViaMe : " & m.ViaMe())
Console.WriteLine("ViaMyClass : " & m.ViaMyClass())
Console.WriteLine("MyBase : " & m.Base())
Console.WriteLine(m.Identify())
Dim legal As New [MyClass]()
Console.WriteLine(legal.Hello() & " (" & GetType([MyClass]).FullName & ")")
End Sub
End Module
ViaMe : Magazine
ViaMyClass : Item
MyBase : Item
Me is a Magazine, MyClass is always Item
escaped class name (Shelf.MyClass)
Source: Me, My, MyBase, and MyClass
ViaMe called the override in Magazine, ViaMyClass the implementation in Item, whatever the runtime type is. In
practice MyClass is rare, and it has no C# equivalent: it is used when a base class wants to be sure that its own
algorithm calls its own version of a helper that derived classes are allowed to replace. MyClass.Method on a MustOverride member is rejected (BC30614): there is nothing to call. Overriding and Shadows are on
Inheritance and Polymorphism.
Instance and Shared members
An instance member belongs to one object. A Shared member belongs to the class itself: there is one copy, it can be
used without an object (Loan.Created), and it is the equivalent of static in C#. The rules:
-
A
Sharedmember cannot useMeor an instance member without an object reference (BC30043, BC30369, BC30469). An instance member can useSharedmembers freely. -
Accessing a
Sharedmember through an object variable (c.Level) compiles, but warns: BC42025, "qualifying expression will not be evaluated". The warning exists becauseGetCounter().Totalwould not callGetCounter(). Always qualify with the class name. -
Staticlocal variables keep their value between calls. In an instance method there is one copy per object; in aSharedmethod, one for the class. They are not thread safe. -
Shared Sub Newruns once before first use;Sharedfield initializers run at the same time, in written order. -
Modulemembers are implicitlyShared; aModuleis the right container for helper procedures that have no state.
Option Strict On
Public Class Counter
Public Shared Total As Integer
Public Count As Integer
Public Sub Hit()
Count += 1
Total += 1
End Sub
Public Shared Sub Reset()
Total = 0
' Count = 0 ' BC30369: Cannot refer to an instance member of a class from within a shared method
End Sub
' Static locals: one per instance in an instance method, one for the whole class in a Shared method
Public Function PerInstance() As Integer
Static calls As Integer
calls += 1
Return calls
End Function
Public Shared Function PerClass() As Integer
Static calls As Integer
calls += 1
Return calls
End Function
End Class
Module Program
Sub Main()
Dim a As New Counter()
Dim b As New Counter()
a.Hit()
a.Hit()
b.Hit()
Console.WriteLine("a.Count=" & a.Count & " b.Count=" & b.Count & " Total=" & Counter.Total)
Console.WriteLine(a.PerInstance() & " " & a.PerInstance() & " " & b.PerInstance())
Console.WriteLine(Counter.PerClass() & " " & Counter.PerClass())
Counter.Reset()
Console.WriteLine("Total after Reset=" & Counter.Total)
End Sub
End Module
a.Count=2 b.Count=1 Total=3
1 2 1
1 2
Total after Reset=0
Source: Shared
The compiler messages for the commented-out line, and for an instance member used in a Shared initializer:
Option Strict On
Public Class Counter
Public Count As Integer
Public Shared Total As Integer = Count + 1
Public Shared Sub Reset()
Count = 0
End Sub
Public Sub Hit()
Count += 1
End Sub
End Class
Module Program
Sub Main()
Counter.Hit()
End Sub
End Module
Program.vb(5,38): error BC30369: Cannot refer to an instance member of a class from within a shared method or shared member initializer without an explicit instance of the class. [App.vbproj]
Program.vb(18,9): error BC30469: Reference to a non-shared member requires an object reference. [App.vbproj]
Program.vb(8,9): error BC30369: Cannot refer to an instance member of a class from within a shared method or shared member initializer without an explicit instance of the class. [App.vbproj]
Source: Shared
Type initialization failures
If a Shared Sub New or a Shared field initializer throws, the CLR wraps the exception in a TypeInitializationException
and remembers the failure. The type is then unusable for the rest of the process: every later access throws the same
exception, and the initializer does not run again.
Option Strict On
Public Class Config
Public Shared ReadOnly Value As Integer = Load()
Private Shared Function Load() As Integer
Console.WriteLine("Loading configuration")
Throw New InvalidOperationException("config file missing")
End Function
End Class
Module Program
Sub Main()
For attempt = 1 To 2
Try
Console.WriteLine(Config.Value)
Catch ex As TypeInitializationException
Console.WriteLine("attempt " & attempt & ": " & ex.GetType().Name & " -> " & ex.InnerException.Message)
End Try
Next
End Sub
End Module
Loading configuration
attempt 1: TypeInitializationException -> config file missing
attempt 2: TypeInitializationException -> config file missing
The log line Loading configuration appears once; the second attempt did not re-run the loader. Initializers should not
do I/O or depend on configuration that may be missing; a Lazy(Of T) or an explicit Initialize method lets the caller
handle the failure and retry.
Access modifiers
Every type and every member has an access level. The six levels, from the widest to the narrowest:
| Modifier | Visible to | CLR name |
|---|---|---|
|
all code that can see the type, in any assembly |
|
|
the same assembly, plus derived classes in any assembly |
|
|
the same assembly (and friend assemblies, see below) |
|
|
this class and derived classes, in any assembly |
|
|
derived classes in the same assembly only (Visual Basic 15.5) |
|
|
this class (and its nested types) only |
|
-
Protectedmembers are accessible inside the class and derived classes, throughMe,MyBaseor an object of the derived type; from other code BC30389 is reported. -
A type nested in a class can use the container’s
Privatemembers; the container cannot use the nested type’s. -
Accessibility cannot widen: a
Publicmember cannot expose aFriendtype in its signature (BC30909). -
A derived class cannot be more accessible than its base class.
The defaults and the six levels, read back from the compiled assembly by reflection:
Option Strict On
Imports System.Reflection
Public Class Sample
Dim PlainField As Integer
Const Limit As Integer = 10
Public Event Changed As EventHandler
Sub PlainSub()
End Sub
Function PlainFunction() As Integer
Return 0
End Function
Property PlainProperty As Integer
Class NestedClass
End Class
Structure NestedStructure
Dim Value As Integer
End Structure
Enum NestedEnum
A
End Enum
Shared Sub New()
End Sub
Sub New()
End Sub
End Class
Structure SampleStructure
Dim Field As Integer
Sub Method()
End Sub
End Structure
Class TopLevelDefault
End Class
Module Program
Private Function Describe(access As MethodBase) As String
If access.IsPublic Then Return "Public"
If access.IsPrivate Then Return "Private"
If access.IsAssembly Then Return "Friend"
If access.IsFamily Then Return "Protected"
Return "other"
End Function
Sub Main()
Const flags As BindingFlags = BindingFlags.Instance Or BindingFlags.Static Or BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.DeclaredOnly
For Each t In {GetType(Sample), GetType(SampleStructure)}
Console.WriteLine("-- " & t.Name & " (top level: " & If(t.IsPublic, "Public", "Friend") & ")")
For Each f In t.GetFields(flags).Where(Function(x) Not x.IsDefined(GetType(System.Runtime.CompilerServices.CompilerGeneratedAttribute), False))
Console.WriteLine("field " & f.Name & ": " & If(f.IsPublic, "Public", If(f.IsPrivate, "Private", "other")))
Next
For Each m In t.GetMethods(flags)
If Not m.IsSpecialName Then Console.WriteLine("method " & m.Name & ": " & Describe(m))
Next
For Each c In t.GetConstructors(flags)
Console.WriteLine("ctor " & If(c.IsStatic, "Shared Sub New", "Sub New") & ": " & Describe(c))
Next
For Each p In t.GetProperties(flags)
Console.WriteLine("property " & p.Name & ": " & Describe(p.GetMethod))
Next
For Each e In t.GetEvents(flags)
Console.WriteLine("event " & e.Name & ": " & Describe(e.AddMethod))
Next
For Each n In t.GetNestedTypes(flags)
Console.WriteLine("nested " & n.Name & ": " & If(n.IsNestedPublic, "Public", If(n.IsNestedPrivate, "Private", "other")))
Next
Next
Console.WriteLine("-- " & GetType(TopLevelDefault).Name & " (top level: " & If(GetType(TopLevelDefault).IsPublic, "Public", "Friend") & ")")
End Sub
End Module
-- Sample (top level: Public)
field PlainField: Private
field Limit: Private
method PlainSub: Public
method PlainFunction: Public
ctor Shared Sub New: Private
ctor Sub New: Public
property PlainProperty: Public
event Changed: Public
nested NestedClass: Public
nested NestedStructure: Public
nested NestedEnum: Public
-- SampleStructure (top level: Friend)
field Field: Public
method Method: Public
-- TopLevelDefault (top level: Friend)
Source: Access levels in Visual Basic
Note that Shared Sub New is Private (a Sub New cannot be called by the code that uses the class), that the event’s
hidden field and the property’s backing field were filtered out, and that a Dim field in a Structure is Public, unlike
in a class.
Option Strict On
Imports System.Reflection
Public Class Levels
Public Sub PublicSub()
End Sub
Protected Friend Sub ProtectedFriendSub()
End Sub
Friend Sub FriendSub()
End Sub
Protected Sub ProtectedSub()
End Sub
Private Protected Sub PrivateProtectedSub()
End Sub
Private Sub PrivateSub()
End Sub
End Class
Module Program
Sub Main()
Const flags As BindingFlags = BindingFlags.Instance Or BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.DeclaredOnly
For Each m In GetType(Levels).GetMethods(flags)
Dim clr As String =
If(m.IsPublic, "public",
If(m.IsFamilyOrAssembly, "protected internal",
If(m.IsAssembly, "internal",
If(m.IsFamily, "protected",
If(m.IsFamilyAndAssembly, "private protected", "private")))))
Console.WriteLine(m.Name.PadRight(22) & clr)
Next
End Sub
End Module
PublicSub public
ProtectedFriendSub protected internal
FriendSub internal
ProtectedSub protected
PrivateProtectedSub private protected
PrivateSub private
Source: Private Protected
The access-level table is also on Variables, Constants and Scope.
Friend and assemblies
Friend is the default for types and is the level of "internal to this project". A library hides its implementation by
making the helper classes Friend and exposing a small set of Public types. Two projects can nevertheless share their
Friend members when the library names the other assembly with InternalsVisibleTo: the usual use is a unit test project
(see Testing). The next solution has a Lib project and an App
project that references it:
Folder layout
Lib/Lib.vbproj
Lib/Catalog.vb
App/App.vbproj (ProjectReference to ..\Lib\Lib.vbproj)
App/Program.vb
Lib/Catalog.vb
Option Strict On
Public Class Catalog
Friend Function InternalCount() As Integer
Return 3
End Function
Public Function PublicCount() As Integer
Return InternalCount()
End Function
End Class
Friend Class Helper
Friend Shared Function Banner() As String
Return "helper in Lib"
End Function
End Class
App/Program.vb
Option Strict On
Imports Shelf.Lib
Module Program
Sub Main()
Dim c As New Catalog()
Console.WriteLine(c.PublicCount())
Console.WriteLine(c.InternalCount())
Console.WriteLine(Helper.Banner())
End Sub
End Module
dotnet build App prints:
App/Program.vb(9,27): error BC30456: 'InternalCount' is not a member of 'Catalog'.
App/Program.vb(10,27): error BC30389: 'Shelf.Lib.Helper' is not accessible in this context because it is 'Friend'.
Source: Friend
The same programs after adding a file to Lib that names the App assembly:
Lib/Catalog.vb
Option Strict On
Public Class Catalog
Friend Function InternalCount() As Integer
Return 3
End Function
Public Function PublicCount() As Integer
Return InternalCount()
End Function
End Class
Friend Class Helper
Friend Shared Function Banner() As String
Return "helper in Lib"
End Function
End Class
Lib/Friends.vb (new)
Imports System.Runtime.CompilerServices
<Assembly: InternalsVisibleTo("App")>
dotnet run --project App prints:
3
3
helper in Lib
Two points. The first build message for the Friend method is "not a member of" (BC30456), because from another assembly
the method is not visible at all, while the Friend class gets the clearer BC30389. And a strong-named library needs the
public key in the attribute argument. The attribute can also be set in the project file instead of a source file:
<ItemGroup>
<InternalsVisibleTo Include="App" />
</ItemGroup>
Source: Friend
The exposure rule
Option Strict On
Friend Class Hidden
End Class
Public Class Exposer
Public Function Make() As Hidden
Return New Hidden()
End Function
Public Property Current As Hidden
Public Sub Use(h As Hidden)
End Sub
End Class
Module Program
Sub Main()
End Sub
End Module
Program.vb(7,31): error BC30909: 'Make' cannot expose type 'Hidden' outside the project through class 'Exposer'. [App.vbproj]
Program.vb(11,32): error BC30909: 'Current' cannot expose type 'Hidden' outside the project through class 'Exposer'. [App.vbproj]
Program.vb(13,25): error BC30909: 'h' cannot expose type 'Hidden' outside the project through class 'Exposer'. [App.vbproj]
Source: Public
A Public method, property or parameter cannot use a Friend type, because a caller outside the assembly could not name
that type. The fix is to make the type Public, make the member Friend, or return a public interface or base class.
Nested types
A type declared inside a class is a nested type. Use one when a type is meaningful only to its container (a private
Entry record in a Catalog), or when you want the container to act as a namespace for a closely related type
(Catalog.Options, Catalog.SortOrder). Rules: nested types default to Public; they are named through the container
(Catalog.Options) or by Imports with an alias; the CLR name uses a plus sign (Shelf.Catalog+Options); a nested type
sees the container’s Private members, but it needs an object reference to use instance members (BC30469 without one).
Option Strict On
Public Class Catalog
Private ReadOnly _items As New List(Of Entry)
Private Shared _nextId As Integer = 1
Public Sub Add(title As String)
_items.Add(New Entry(title))
End Sub
' A nested type can use the private members of its container
Private Class Entry
Public ReadOnly Property Id As Integer
Public ReadOnly Property Title As String
Public Sub New(title As String)
Id = _nextId
_nextId += 1
Me.Title = title
End Sub
End Class
Public Function Titles() As IEnumerable(Of String)
Return _items.Select(Function(e) e.Id & ":" & e.Title)
End Function
' A public nested type is part of the API and is named through its container
Public Enum SortOrder
ByTitle
ByYear
End Enum
Public Class Options
Public Property Order As SortOrder = SortOrder.ByTitle
Public Overrides Function ToString() As String
Return "Options(" & Order.ToString() & ")"
End Function
End Class
End Class
Module Program
Sub Main()
Dim c As New Catalog()
c.Add("Refactoring")
c.Add("Clean Code")
Console.WriteLine(String.Join("; ", c.Titles()))
Dim o As New Catalog.Options With {.Order = Catalog.SortOrder.ByYear}
Console.WriteLine(o)
Console.WriteLine(GetType(Catalog.Options).FullName)
Console.WriteLine(GetType(Catalog.Options).DeclaringType.Name)
End Sub
End Module
1:Refactoring; 2:Clean Code
Options(ByYear)
Shelf.Catalog+Options
Catalog
Source: Class Statement
Do not nest types merely to hide them from a namespace listing, and avoid public nested types in libraries unless callers
really name them rarely: Catalog.Options is awkward in an Imports list. The common alternative, a separate CatalogOptions
class in the same namespace, is usually clearer.
Partial classes
Partial (Visual Basic 2005) lets one class be declared in several files of the same project. They are merged by the compiler into one type.
Typical uses are generated code (Windows Forms designer files and XAML code-behind files that contain the Partial
part generated by a tool, next to the part you edit) and keeping a very large class manageable.
-
All parts must belong to the same project (assembly) and namespace, have the same class name and type parameters, and the same base class and interfaces if they name them (the union of
Implementsis used). -
Partialis required on at least one part. In a test with two parts, the project built when only one of them had the keyword (either one); with neither, the compiler reported BC30179, "class 'Loan' and class 'Loan' conflict in namespace 'Shelf'". -
Access modifiers written on several parts must agree.
MustInheritorNotInheritableon one part applies to the whole class. -
Partial methods (a declaration in one part and an implementation in another) are on Extension Methods and Partial Methods.
Program.vb
Option Strict On
Module Program
Sub Main()
Dim loan As New Loan("Refactoring", #2026-10-19#)
Console.WriteLine(loan.Describe())
Console.WriteLine(loan.IsOverdue(#2026-11-20#))
Console.WriteLine(GetType(Loan).Assembly.GetTypes().Count(Function(t) t.Name = "Loan") & " Loan type in the assembly")
End Sub
End Module
Loan.vb
Option Strict On
' Loan.vb: data and constructor
Public Partial Class Loan
Private ReadOnly _title As String
Private ReadOnly _dueOn As Date
Public Sub New(title As String, dueOn As Date)
_title = title
_dueOn = dueOn
End Sub
End Class
Loan.Rules.vb
Option Strict On
' Loan.Rules.vb: behaviour. Partial is written on both parts here.
Partial Public Class Loan
Public Function Describe() As String
Return _title & " due " & _dueOn.ToString("yyyy-MM-dd")
End Function
Public Function IsOverdue(today As Date) As Boolean
Return today > _dueOn
End Function
End Class
Refactoring due 2026-10-19
True
1 Loan type in the assembly
Source: Partial
The compiled assembly has one Loan type. Private members declared in one file are visible in the other, because
after merging they are members of one class.
Identity and equality
Two questions are different and both are common:
-
Identity: do two variables refer to the same object? This is the
Isoperator (andIsNot). It compares references only, it cannot be overloaded, and it works on any pair of reference or nullable operands, includingNothing. For value types the compiler rejects it (BC30020). -
Equality: do two objects have the same value, as defined by their class? That is
Equals(Object), and, if the class overloads it, the=operator. The defaultObject.Equalsof a class is a reference comparison, so it returnsFalsefor two distinct but identical-looking objects unless the class overrides it.
= on two class-typed operands that do not define an operator is a compile error (BC30452), in contrast with C#, where
== on classes silently compares references. Visual Basic forces you to say which one you want: Is for identity,
Equals or the overloaded = for value equality. Overriding Equals requires overriding GetHashCode as well,
because hash-based collections (HashSet, Dictionary) use both; the full set of rules, IEquatable(Of T), and
user-defined operators are on
Operator Overloading and Equality.
Option Strict On
Public Class Book
Public ReadOnly Property Title As String
Public Sub New(title As String)
Me.Title = title
End Sub
End Class
Public Class Edition
Public ReadOnly Property Title As String
Public Sub New(title As String)
Me.Title = title
End Sub
Public Overrides Function Equals(obj As Object) As Boolean
Dim other = TryCast(obj, Edition)
Return other IsNot Nothing AndAlso other.Title = Title
End Function
Public Overrides Function GetHashCode() As Integer
Return Title.GetHashCode()
End Function
End Class
Module Program
Sub Main()
Dim a As New Book("Refactoring")
Dim b As New Book("Refactoring")
Dim c = a
Console.WriteLine("a Is b: " & (a Is b))
Console.WriteLine("a Is c: " & (a Is c))
Console.WriteLine("a IsNot b: " & (a IsNot b))
Console.WriteLine("a.Equals(b): " & a.Equals(b))
Console.WriteLine("Object.ReferenceEquals(a, c): " & Object.ReferenceEquals(a, c))
Console.WriteLine("same hash for a and c: " & (a.GetHashCode() = c.GetHashCode()))
Dim e1 As New Edition("Refactoring")
Dim e2 As New Edition("Refactoring")
Console.WriteLine("e1 Is e2: " & (e1 Is e2))
Console.WriteLine("e1.Equals(e2): " & e1.Equals(e2))
Console.WriteLine("Equals(e1, e2): " & Object.Equals(e1, e2))
Console.WriteLine("Equals(Nothing, Nothing): " & Object.Equals(Nothing, Nothing))
Dim none As Book = Nothing
Console.WriteLine("none Is Nothing: " & (none Is Nothing))
Console.WriteLine("a IsNot Nothing: " & (a IsNot Nothing))
' Collections use Equals and GetHashCode
Dim set1 As New HashSet(Of Book) From {a, b, c}
Dim set2 As New HashSet(Of Edition) From {e1, e2}
Console.WriteLine("Books in set: " & set1.Count & ", Editions in set: " & set2.Count)
' = is not defined for a class without an Operator
' Console.WriteLine(a = b) ' BC30452
End Sub
End Module
a Is b: False
a Is c: True
a IsNot b: True
a.Equals(b): False
Object.ReferenceEquals(a, c): True
same hash for a and c: True
e1 Is e2: False
e1.Equals(e2): True
Equals(e1, e2): True
Equals(Nothing, Nothing): True
none Is Nothing: True
a IsNot Nothing: True
Books in set: 2, Editions in set: 1
Source: Is Operator
The HashSet(Of Book) kept two entries (a and c are one object, b another), the HashSet(Of Edition) one, because
Edition overrides Equals and GetHashCode. Identity versus equality has a few subtle cases:
Option Strict On
Module Program
Sub Main()
Dim literal1 As String = "shelf"
Dim literal2 As String = "shelf"
Dim built As String = New String("shelf".ToCharArray())
Console.WriteLine("literals Is: " & (literal1 Is literal2)) ' literals are interned
Console.WriteLine("built Is literal: " & (built Is literal1)) ' different object
Console.WriteLine("built = literal: " & (built = literal1)) ' = on String compares text
Console.WriteLine("Equals: " & built.Equals(literal1))
Dim boxed1 As Object = 5
Dim boxed2 As Object = 5
Console.WriteLine("boxed Is: " & (boxed1 Is boxed2)) ' two separate boxes
Console.WriteLine("boxed Equals: " & boxed1.Equals(boxed2))
Console.WriteLine("Nullable Is Nothing: " & (New Integer?() Is Nothing))
End Sub
End Module
literals Is: True
built Is literal: False
built = literal: True
Equals: True
boxed Is: False
boxed Equals: True
Nullable Is Nothing: True
Source: Object.ReferenceEquals Method
-
Two string literals with the same text are the same object (the compiler interns them), but a string built at run time is not.
Ison strings is therefore almost never what you want;=onStringcompares the characters, andString.EqualsorString.Comparegive control of culture and case (see Strings and Text). -
Boxing a value creates a new object each time, so
Ison two boxed5`s is `FalsewhileEqualsisTrue. -
Ison a nullable value type withNothingtestsHasValue.
The value-type error:
Option Strict On
Module Program
Sub Main()
Dim a As Integer = 1
Dim b As Integer = 1
Console.WriteLine(a Is b)
End Sub
End Module
Program.vb(7,27): error BC30020: 'Is' operator does not accept operands of type 'Integer'. Operands must be reference or nullable types. [App.vbproj]
Program.vb(7,32): error BC30020: 'Is' operator does not accept operands of type 'Integer'. Operands must be reference or nullable types. [App.vbproj]
Source: Is Operator
The Object members every class has
All classes derive from System.Object (the Visual Basic keyword Object is the same type), so every object has these
members.
| Member | Notes |
|---|---|
|
returns the type name by default; override it for a readable text. Used by |
|
reference equality by default; override together with |
|
a hash code consistent with |
|
the runtime |
|
|
|
|
|
|
Copying and comparing objects
MemberwiseClone copies the fields of an object into a new object but does not copy the objects that the fields refer to,
so a "clone" shares its collections with the original. A deep copy copies those too. IComparable(Of T) makes the
natural order of a class available to Sort, SortedSet and Order By; a Comparison(Of T) lambda gives an alternative
order for one call. Avoid ICloneable: it does not say whether the copy is shallow or deep.
Option Strict On
Public Class Author
Public Property Name As String = ""
End Class
Public Class Book
Implements IComparable(Of Book)
Public Property Title As String = ""
Public Property Year As Integer
Public Property Authors As New List(Of Author)
Public Function ShallowCopy() As Book
Return DirectCast(MemberwiseClone(), Book)
End Function
Public Function DeepCopy() As Book
Dim copy = ShallowCopy()
copy.Authors = New List(Of Author)(Authors.Select(Function(a) New Author With {.Name = a.Name}))
Return copy
End Function
Public Function CompareTo(other As Book) As Integer Implements IComparable(Of Book).CompareTo
If other Is Nothing Then Return 1
Return Year.CompareTo(other.Year)
End Function
Public Overrides Function ToString() As String
Return Title & " (" & Year & ")"
End Function
End Class
Module Program
Sub Main()
Dim original As New Book With {.Title = "Clean Code", .Year = 2008}
original.Authors.Add(New Author With {.Name = "Robert C. Martin"})
Dim shallow = original.ShallowCopy()
Dim deep = original.DeepCopy()
shallow.Title = "Changed"
original.Authors(0).Name = "R. Martin"
Console.WriteLine(original.ToString() & " | " & shallow.ToString() & " | " & deep.ToString())
Console.WriteLine("shallow shares authors: " & (shallow.Authors Is original.Authors) & ", " & shallow.Authors(0).Name)
Console.WriteLine("deep author: " & deep.Authors(0).Name)
Console.WriteLine(original.GetType().FullName & " / " & GetType(Book).Name)
Console.WriteLine(original.GetType() Is GetType(Book))
Dim shelf As New List(Of Book) From {
New Book With {.Title = "Refactoring", .Year = 1999},
New Book With {.Title = "Clean Code", .Year = 2008},
New Book With {.Title = "DDD", .Year = 2003}
}
shelf.Sort()
Console.WriteLine(String.Join(" < ", shelf))
shelf.Sort(Function(x, y) String.Compare(x.Title, y.Title, StringComparison.Ordinal))
Console.WriteLine(String.Join(" < ", shelf))
End Sub
End Module
Clean Code (2008) | Changed (2008) | Clean Code (2008)
shallow shares authors: True, R. Martin
deep author: Robert C. Martin
Shelf.Book / Book
True
Refactoring (1999) < DDD (2003) < Clean Code (2008)
Clean Code (2008) < DDD (2003) < Refactoring (1999)
Source: Object.MemberwiseClone Method
The shallow copy shallow changed Title independently, but its Authors list was the same object as the original’s,
so the rename of the author through original was visible in shallow. The deep copy kept the old name. The line with
& and a Book would have failed with BC30452, which is why the program calls ToString().
Type tests and casts
An object can be treated as any of its base classes or interfaces. The conversion from derived to base is implicit; the other direction needs a check at run time.
| Expression | Behaviour |
|---|---|
|
|
|
the object as |
|
the object as |
|
like |
|
the exact runtime type, for an equality test with |
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
Private Sub Show(item As CatalogItem)
If TypeOf item Is Book Then
Console.WriteLine("a Book: " & item.GetType().Name)
ElseIf TypeOf item Is Magazine Then
Console.WriteLine("a Magazine")
End If
Dim asBook = TryCast(item, Book)
Console.WriteLine("TryCast to Book: " & If(asBook Is Nothing, "Nothing", "ok"))
Try
Dim forced = DirectCast(item, Book)
Console.WriteLine("DirectCast ok: " & forced.GetType().Name)
Catch ex As InvalidCastException
Console.WriteLine("InvalidCastException: " & ex.Message)
End Try
End Sub
Sub Main()
Show(New Book())
Show(New Magazine())
Console.WriteLine(GetType(Book).BaseType.Name & " / " & GetType(CatalogItem).IsAbstract)
End Sub
End Module
a Book: Book
TryCast to Book: ok
DirectCast ok: Book
a Magazine
TryCast to Book: Nothing
InvalidCastException: Unable to cast object of type 'Shelf.Magazine' to type 'Shelf.Book'.
CatalogItem / True
Source: TypeOf Operator
The message of InvalidCastException includes the root namespace (Shelf.Magazine). Conversions between value types
are on Type Conversions; the "which cast?" flowchart is there.
Case-insensitivity and name clashes
Names are not case sensitive, and a parameter hides a member of the same spelling. The most common consequence is in
constructors and setters. With a field or property Name and a parameter name, the unqualified Name refers to the
parameter (the innermost scope), so the statement Name = name assigns the parameter to itself. It compiles without a
warning and silently loses the value. Use Me.Name = name, or give the fields an underscore prefix.
Option Strict On
Public Class ReaderBroken
Public Property Name As String = "unset"
Public Sub New(name As String)
' VB is case-insensitive: "Name" here is the parameter "name", so this assigns the parameter to itself.
Name = name
End Sub
End Class
Public Class ReaderFixed
Public Property Name As String = "unset"
Public Sub New(name As String)
Me.Name = name
End Sub
End Class
Module Program
Sub Main()
Console.WriteLine("without Me: " & New ReaderBroken("Ada").Name)
Console.WriteLine("with Me : " & New ReaderFixed("Ada").Name)
End Sub
End Module
without Me: unset
with Me : Ada
Source: Declared Element Characteristics
A local variable with the same name as its type is legal (Dim book As Book), and is the convention for a single
object of a type. Because identifiers are not case sensitive, such a variable and the type differ only in context.
Errors the compiler reports
Each snippet below is a complete program that fails to compile; the messages are the real compiler output from the .NET 10 SDK (built with warnings as errors turned off so that warnings print as warnings).
Option Strict On
Public Class Reader
Public Sub New(name As String)
End Sub
End Class
Public Class Student
Inherits Reader
End Class
Public Class Tutor
Inherits Reader
Public Sub New()
Console.WriteLine("tutor")
End Sub
End Class
Public Class Counter
Public Total As Integer
Public ReadOnly Limit As Integer = 3
Public Shared Sub Reset()
Me.Total = 0
End Sub
Public Sub Change()
Limit = 4
End Sub
Public Sub New()
Return 1
End Sub
End Class
Public MustInherit Class Shape
Public MustOverride Function Area() As Double
Public Function Twice() As Double
Return MyClass.Area() * 2
End Function
End Class
Public Class Secret
Private Hidden As Integer
Protected Guarded As Integer
End Class
Module Program
Sub Main()
Dim r As New Reader()
Dim s As New Shape()
Dim sec As New Secret()
sec.Hidden = 1
sec.Guarded = 2
Dim a As New Counter()
Dim b As New Counter()
Console.WriteLine(a = b)
Console.WriteLine(a.Limit)
End Sub
End Module
Program.vb(8,14): error BC30387: Class 'Student' must declare a 'Sub New' because its base class 'Reader' does not have an accessible 'Sub New' that can be called with no arguments. [App.vbproj]
Program.vb(25,9): error BC30043: 'Me' is valid only within an instance method. [App.vbproj]
Program.vb(29,9): error BC30064: 'ReadOnly' variable cannot be the target of an assignment. [App.vbproj]
Program.vb(33,9): error BC30647: 'Return' statement in a Sub or a Set cannot return a value. [App.vbproj]
Program.vb(15,16): error BC30148: First statement of this 'Sub New' must be a call to 'MyBase.New' or 'MyClass.New' because base class 'Reader' of 'Tutor' does not have an accessible 'Sub New' that can be called with no arguments. [App.vbproj]
Program.vb(52,22): error BC30455: Argument not specified for parameter 'name' of 'Public Sub New(name As String)'. [App.vbproj]
Program.vb(53,18): error BC30569: 'New' cannot be used on a class that is declared 'MustInherit'. [App.vbproj]
Program.vb(55,9): error BC30389: 'Shelf.Secret.Hidden' is not accessible in this context because it is 'Private'. [App.vbproj]
Program.vb(56,9): error BC30389: 'Shelf.Secret.Guarded' is not accessible in this context because it is 'Protected'. [App.vbproj]
Program.vb(59,27): error BC30452: Operator '=' is not defined for types 'Counter' and 'Counter'. [App.vbproj]
Program.vb(41,16): error BC30614: 'MustOverride' method 'Public MustOverride Function Area() As Double' cannot be called with 'MyClass'. [App.vbproj]
Source: BC30387
The compiler listed BC30387, a check on the class declaration, after the errors found in method bodies. More:
Option Strict On
Public Class Chain
Public Sub New(x As Integer)
Console.WriteLine("work first")
Me.New()
End Sub
Public Sub New()
Me.New(1)
End Sub
End Class
Public Class Config
Public Shared Level As Integer = 1
Public Name As String = "x"
End Class
Public Class Book
Public ReadOnly Property Title As String = ""
End Class
Module Program
Sub Main()
Dim c As New Config()
Console.WriteLine(c.Level)
Dim b As New Book With {.Title = "x"}
End Sub
End Module
Program.vb(6,9): error BC30282: Constructor call is valid only as the first statement in an instance constructor. [App.vbproj]
Program.vb(26,27): warning BC42025: Access of shared member, constant member, enum member or nested type through an instance; qualifying expression will not be evaluated. [App.vbproj]
Program.vb(27,33): error BC30526: Property 'Title' is 'ReadOnly'. [App.vbproj]
Source: BC30282
A constructor chain with a cycle, and a reserved word as a class name:
Option Strict On
Public Class Chain
Public Sub New(x As Integer)
Me.New()
End Sub
Public Sub New()
Me.New(1)
End Sub
End Class
Module Program
Sub Main()
Dim c As New Chain()
End Sub
End Module
Program.vb(4,16): error BC30298: Constructor 'Public Sub New(x As Integer)' cannot call itself: [App.vbproj]
Program.vb(8,16): error BC30298: Constructor 'Public Sub New()' cannot call itself: [App.vbproj]
Source: Sub Statement
Option Strict On
Public Class MyClass
Public Sub Run()
End Sub
End Class
Module Program
Sub Main()
Dim x As New MyClass()
End Sub
End Module
Program.vb(3,14): error BC30183: Keyword is not valid as an identifier. [App.vbproj]
Program.vb(10,22): error BC30180: Keyword does not name a type. [App.vbproj]
Source: BC30183
The accessibility of constructors (a private Sub New) in a base class and in a user:
Option Strict On
Public NotInheritable Class Isbn
Private Sub New()
End Sub
End Class
Public Class Outer
Private _value As Integer
Public Class Inner
Public Function Read() As Integer
Return _value
End Function
End Class
End Class
Public Class Plain
Private Sub New()
End Sub
End Class
Public Class Child
Inherits Plain
End Class
Module Program
Sub Main()
Dim bad As New Isbn()
End Sub
End Module
Program.vb(23,14): error BC31399: Class 'Plain' has no accessible 'Sub New' and cannot be inherited. [App.vbproj]
Program.vb(29,24): error BC30517: Overload resolution failed because no 'New' is accessible. [App.vbproj]
Program.vb(13,20): error BC30469: Reference to a non-shared member requires an object reference. [App.vbproj]
Source: BC30517
| Code | Cause |
|---|---|
BC30020 |
|
BC30043 |
|
BC30064 |
assignment to a |
BC30148 |
a |
BC30179 |
all parts of a class lack |
BC30180, BC30183 |
a keyword (such as |
BC30282 |
|
BC30298 |
constructors that call each other in a cycle |
BC30299 |
a class derived from a |
BC30369 |
instance member used in a |
BC30387 |
derived class without a usable base |
BC30389 |
inaccessible member or type ( |
BC30452 |
|
BC30455 |
a constructor argument is missing |
BC30456 |
a member does not exist from the caller’s point of view (e.g. a |
BC30469 |
instance member used without an object reference |
BC30517 |
no accessible constructor |
BC30526 |
object initializer assigns a |
BC30569 |
|
BC30614 |
|
BC30647 |
|
BC30909 |
a |
BC31399 |
inheriting from a class with no accessible constructor |
BC42025 |
|
BC42104 |
a variable used before it has been assigned (warning) |
Learn has an error page for most of these numbers (the Source lines above link some of them) but not for all, which is why the compiler’s own text is shown. BC42025 and BC42104 are warnings; this project turns them into errors.
Runtime exceptions
| Exception | Typical cause |
|---|---|
|
a member call, field read or |
|
|
|
an exception in a |
|
unbounded recursion, for example a property whose getter reads itself instead of the backing field, or two constructors that create each other; it cannot be caught and ends the process. |
|
use of an object after |
|
late binding to a member that is not there (see Early and Late Binding) |
Differences from C#
| Topic | Visual Basic | C# |
|---|---|---|
Current object, base class |
|
|
Current-class call without virtual dispatch |
|
no equivalent |
Constructor |
|
a method named after the class; chained with |
Static members, static constructor |
|
|
Static class |
|
|
Internal, sealed, abstract |
|
|
Protected internal, private protected |
|
|
Default member accessibility |
fields from |
|
Default accessibility of a top-level type |
|
|
Instance initializers vs the base constructor |
run after the base constructor |
run before it |
Instance initializer using another instance field |
allowed |
not allowed |
|
not defined: BC30452; use |
compares references |
Object initializer |
|
|
|
yes |
no |
Target-typed |
not available |
yes |
Primary constructors, |
not declarable in VB (VB consumes |
yes |
|
required on at least one part |
required on every part |
Case sensitivity |
no: |
yes |
Method hiding |
|
|
Finalizer |
|
|
|
yes |
no (use a field) |
Nested type default accessibility |
|
|
Older material you may meet
Visual Basic 6 and VBA had classes too, but of a different kind. Old tutorials, and some books that were written from them, differ in these points:
| Old code you may meet | What it is today |
|---|---|
|
assignment needs no |
|
|
|
VB.NET creates the object at the declaration. The VB6 behaviour (auto-instantiation) is gone; a |
a class module’s |
|
default instances of forms ( |
still present in Windows Forms projects through |
|
|
|
a |
The books this reference was compared with (see the bibliography on
the landing page) contain two kinds of outdated or
incorrect material for this page. First, a few examples name the class MyClass. That is a reserved word and the code
does not compile (BC30183), as shown above; the working versions use another name or the escaped [MyClass]. Second,
the book examples are written for the "Console (.NET Framework)" project template and for Visual Studio 2019 or 2022
with .NET Framework 4.x; everything about classes works unchanged on .NET 10 with an SDK-style .vbproj, but the
projects, Option Strict default (Off in the SDK defaults, so write Option Strict On in every file) and the root namespace
that prefixes type names in messages are different.
Pitfalls
-
Writing
Name = namein a constructor with a parametername: the field stays unset (useMe.Nameor_name). -
Forgetting that adding any
Sub Newremoves the default parameterless constructor (BC30455 at the oldNew T()calls). -
Calling an overridable method from a constructor: the derived class’s initializers have not run.
-
Comparing objects with
=(BC30452) and then "fixing" it by comparingToString()results; useIsfor identity and overrideEqualsandGetHashCodefor value equality. -
Using
Ison strings or boxed numbers. -
Relying on the defaults of access modifiers;
Dimfields arePrivate,SubisPublic, nested types arePublic. -
A class with a
Shared Sub NeworSharedfield initializer that can throw: the type is unusable after the first failure (TypeInitializationException). -
Giving a
Publicmember aFriendtype (BC30909). -
Using
MyClassas a name; rename it or write[MyClass]. -
Accessing a
Sharedmember through an instance (BC42025). -
A shallow
MemberwiseClonethat shares collections with the original. -
Hiding bugs by exposing public fields; use properties (see Properties and Default Properties).
References
-
Objects and Classes (Visual Basic Guide)
-
Object Initializers: Named and Anonymous Types and With…End With Statement
-
Access levels in Visual Basic and the modifier pages Public, Friend, Protected, Protected Friend, Private Protected and Private
-
Shared, Static, ReadOnly, Partial, MustInherit and NotInheritable
-
Is Operator, IsNot Operator, New Operator and TypeOf Operator
-
Object Class, Object.MemberwiseClone, Object.ReferenceEquals, IComparable(Of T) and TypeInitializationException
-
Constructor Design (.NET Framework Design Guidelines)
-
Visual Basic Language Specification: Types and Visual Basic Language Specification: Type Members