What is a class?
The term is known to everyone that read anything about programming in VBA. It defines objects, yes. But what else do we know about them? What don’t we know about them?
VBA being built on top of COM has a number of implications, and explains a number of language features and the mechanics around classes and objects. Let’s dive into what makes a class module, and what being written in a class module means for your code.
Metadata
In order to define a class, a class module needs to contain metadata, information that VBA will use when it needs to create an object. This metadata is thoroughly specified in MS-VBAL section 4.2.
If we add a new class module to a VBA project, name it Thing
, then export the code file, it should look something like this (minus Option Explicit
if you don’t have the “require variable declaration” VBE setting enabled for some reason):
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "Thing"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = True
Option Explicit
This VERSION 1.0 CLASS
is the header VBA is looking for – if you export a standard module, it will not have that header. It won’t have the BEGIN...END
block that follows either.
MultiUse = -1
is controlled by the Instancing
property, which you can find in the Properties toolwindow:

VBA supports two values for this property (VB6 has more, depending on the project type):
- Private (default)
- PublicNotCreateable
By default, a VBA class is private, thus only visible / only exists in the project in which it’s defined. In an add-in project, this is useful for a private/internal API. When an add-in project needs to be referenced from other VBA projects, it needs a public API – provided by PublicNotCreateable class modules. These classes can be used in referencing projects, but you can only New
them up in the project they’re defined in.
We could imagine that, if a class module’s property grid had more rows than that, the other values would all be listed between the BEGIN
and END
keywords. So where’s the (Name)
coming from?
Attribute VB_Name = "Thing"
The Attribute
token, as per specifications, must always appear at the beginning of a line. Attribute names are a bit cryptic, and they’re not all specified or well-documented. In fact, different VBA host applications may support different attributes… but all VBA implementations support VB_Name
and the attributes that control instantiation:
- VB_GlobalNameSpace (false)
- VB_Createable (false if public)
- VB_PredeclaredId (false by default)
- VB_Exposed (true if public)
3 of these 4 attributes are controlled by the Instancing property. VB_PredeclaredId
is normally False
, but special classes such as a UserForm
have it set to True
; the VBE provides no tooling to manipulate this attribute, but VBA will honor its value if you modify it manually (or if Rubberduck does it for you).
Instance
The instancing property of the class module determines whether & how the class is able to be instantiated; an instance of a class is an object that is defined by that class. In VBA you can create a new object with the New
keyword or the CreateObject
function – but CreateObject
will not work with VBA user code, because the instancing modes supported in VBA don’t allow it.
Being an object, an instance of a class can only ever exist at run-time. Objects only ever exist at run-time.
There are several ways objects can come into existence in VBA.
- Issued to the left-hand side of a
Set
assignment, using aNew
keyword or aCreateObject
function call on the right-hand side; - Issued to a
With
block from aNew
keyword orCreateObject
function call; - On the first encounter (or first since last destruction) of the name of a class module with the
VB_PredeclaredId
attribute value set toTrue
; - On the first encounter (or first since last destruction) of an object reference declared with
As New
;
Similar to how an Excel project’s ThisWorkbook
module “inherits” the Workbook
members, VBA classes “inherit” a standard “VB user class” that provides two hooks on two particularly important stages in the object’s lifetime: Initialize
, and Terminate
.
Private Sub Class_Initialize()
End Sub
Private Sub Class_Terminate()
End Sub
Given this code:
Private Sub Test()
Dim foo As Class1
Set foo = New Class1
With New Class1
End With
End Sub
The Initialize
handler would run twice – once at New Class1
as the right-hand-side of the Set assignment is being evaluated, so before the foo
reference is assigned. Then once more at New Class1
in the With
block, as the block is entered but before the object reference is captured by the With
block.
The Terminate
handler would first run at End With
for the With
instance, and the foo
instance would terminate at End Sub
, because that’s where it’s going out of scope (and it’s not Set
to Nothing
before that, and nothing else is keeping alive a reference to the object).
Keep in mind that declarations aren’t executable statements, so this code runs both handlers exactly 0 times:
Private Sub Test()
Dim foo As New Class1
End Sub
The As New
object is only actually created where foo
is first referenced. If Class1
exposed a Public Sub DoSomething()
method, and the above procedure had foo.DoSomething
under the declaration, Initialize
would run at the dereferencing operator (.
), just before the member call to the DoSomething
method; the Terminate
handler would run at End Sub
.
Auto-instantiated objects (declared with As New
), like the default instance of class modules with a PredeclaredId
attribute set to True
, are re-created whenever they are no longer in scope but suddenly need to exist again:
Private Sub Test()
Dim foo As New Class1
foo.DoSomething
Set foo = Nothing
Debug.Print foo Is Nothing
End Sub
Without As New
, the foo.DoSomething
member call would be made against Nothing
and this code would raise run-time error 91. Because of As New
, the assignment to Nothing
does terminate the object instance, but a new one is spawned again when foo
is dereferenced again – so even though the object was just destroyed, the Debug.Print
statement correctly (but confusingly) outputs False
, since merely referencing the object foo
has caused VBA to re-create it behind your back.
This behavior is marginally useful, but definitely not as obvious at it should be: that is why Rubberduck warns about As New
declarations.
Members
The members of a class are the fields (module variables), properties, and methods of the class – whether private or public. Fields should be Private
and encapsulate the instance state. Property Get
members can expose private fields in a read-only manner, and Function
and Sub
procedures are dubbed methods.
Avoid write-only properties; if external code can write to a property, it should be able to read that value back.
A class module can define a default member by specifying a VB_UserMemId
member attribute with a value of 0
(or with Rubberduck, by simply adding a '@DefaultMember
annotation/comment). For collection classes, convention is to have an Item
property as the default member, such that things(i)
is equivalent to things.Item(i)
. The hidden attribute, like all attributes, is only visible when the code file is exported to be viewed in a text editor that isn’t the VBE:
'@DefaultMember
Public Property Get Item(ByVal index As Variant) As Variant
Attribute Item.VB_UserMemId = 0
'...
End Property
In any other case, default members should be avoided: a class with a default member can be let-coerced into a value, and this easily makes things confusing – especially when considering that a default member might be returning an object that itself has a default member, that returns an object, that itself has a default member, that returns an object, …the compiler needs to work some recursive logic just to resolve what types/classes are involved: needless to say, us mere puny humans reading the code, understandably have no idea.
Let-coercion happens when you try to evaluate an object as a value expression, for example:
Debug.Print Application
We know that this instruction prints “Microsoft Excel” just like it would if we fully spelled out Debug.Print Application.Name
, but we can’t know how. We can’t, because if we inspect the Application
class in the Object Browser, we find that its default property is a hidden _Default
get-only String
property, and we can only best-guess that the implementation of that property is yielding the Name
property. The result is an opaque API that does things implicitly, and nobody knows what these things are. This is bad, because at face value, Debug.Print Application
makes no sense as a statement if we know that Application
is an object.
If we try to let-coerce an object that doesn’t have a default member, we get run-time error 438 “object doesn’t support this property or method”, which we normally see when a late-bound member call fails. An with a hidden default member, we ensure that the inner workings of our APIs remain an arcane black box for everyone consuming our APIs. Don’t do this to your future self.
Without this language feature, the Set
keyword wouldn’t be needed – assigning an object reference would never be ambiguous!
Dim rng As Variant
rng = Sheet1.Range("A1:A10") 'ambiguous: is rng the Range or its default value?
Set rng = Sheet1.Range("A1:A10") 'unambiguous: definitely the Range reference
Interfaces
Every class defines a default interface – the Public
members determine what’s on that interface. When a variable is declared As Class1
, the members available to IntelliSense are the members of the default interface of Class1
. Interfaces cannot expose fields and/or events, so if a class defines public fields, they are exposed as read/write properties; events are simply ignored.
An interface defines how an object can interact with the outside world – or rather, how the outside world can interact with an object: it’s like a boundary, an interface between the object and its client. The object encapsulates state, its interface allows the outside world to manipulate it.
Having a default interface implies that objects can have non-default interfaces, too: an object can implement as many interfaces as needed. In VBA that’s done with the Implements
keyword. And because every class defines a public interface, any class can implement any other.
Say we had a Thing
class, we would declare an object variable of that type like this:
Dim instance As Thing
Set instance = New Thing
This makes the compiler understand that when we access the instance
object, we are seeing it through the lens of its default Thing
interface, so the member we get in the drop-down list when we type the .
dereferencing operator in instance.
, are the public members defined in the Thing
class module.
If Thing
implemented an ISomething
interface, and we declared it like this:
Dim instance As ISomething
Set instance = New Thing
We would still be looking at a Thing
, however we would now be seeing it through the lens of its ISomething
interface, and the members we would get in the drop-down list when we typed instance.
, would be the public members defined in the ISomething
class module.
The benefits of this might seem very much theoretical and far-fetched if we’re coming from purely procedural programming, but from a COM standpoint, this is very much Tuesday – meaning, declaring explicit interfaces that formally define how we intend the outside world to be able to interact with our objects, is standard practice. .NET code intended to be consumed by COM, makes a cleaner API when it defines explicit interfaces.
When a class defines a public interface, but no implementation for these members, we give that class a generic name that we traditionally prefix with a very much Hungarian I
. This makes it easy to identify purely abstract classes (interfaces) in a sea of modules. With Rubberduck’s Code Explorer, classes that are implemented by other classes, as well as classes annotated with an '@Interface
annotation/comment, get a special dedicated “interface” icon that can help easily identify interface modules in a code base, prefixed or not.
This prefixing tradition was carried into modern practices, and you will find this I
prefix everywhere in the .NET framework, just as you will find it in a lot of (often hidden) classes in standard VBA libraries – so in order to blend in and deliver a clear consistent message to the reader/maintainer, interface class module names should be prefixed with an I
:

If you dig into the hidden classes of the Excel type library, you will find hidden interfaces that expose members that should look familiar: IAppEvents
, IWorkbookEvents
, IChartEvents
, ICommandBarEvents
, IDocEvents
, IOLEObjectEvents
, and many others; these hidden interfaces are your biggest clue about how interfaces and events are related, and indirectly, about how events work under COM: this mechanism explains why a VBA interface cannot expose events directly. So what are events then?
Events
Easily the very first aspect of writing VBA code we are exposed to, event handler procedures are defined by a special implicit interface that we specify in VBA code using the Event
keyword in a class module. When we type the space following the RaiseEvent
keyword, the editor assists by listing the members of that interface:

Raising an event can be seen as a special kind of call statement that invokes a callback – a procedure that’s written in some other class module: an event handler procedure, that may or may not execute code that we know nothing about. The handler procedure runs synchronously, so the next statement to run after a RaiseEvent
statement, will run after the handler procedure has returned (this is particularly useful for events that define a ByRef cancel As Boolean
parameter)… unless the handler bombed:
Option Explicit
Public Event Something(ByVal foo As String, ByVal bar As Long)
Public Sub DoSomething()
On Error Resume Next
RaiseEvent Something("foo", 42)
Debug.Print "done"
End Sub
This is where the notion of callback matters: the above code will never print “done” if a handler runs into an unhandled error – execution will simply stop (and VBA will pop that familiar “unhandled error” box). If the Something
handler were a “normal” procedure call, “done” would get printed whether or not an error was unhandled in the procedure.
We can handle Workbook
events in the ThisWorkbook
document class module, because the code of ThisWorkbook
already includes every member of a Workbook
class, even before we write a single line of code in it:
Debug.Print TypeOf ThisWorkbook Is Excel.Workbook 'True
This type of class inheritance unfortunately isn’t possible with VBA user code, but we can also express the relationship with composition, if we visualize the ThisWorkbook
module like this:
Option Explicit
Private WithEvents Workbook As Workbook
Private Sub Class_Initialize()
Set Workbook = Me
End Sub
The net effect being that in the ThisWorkbook
module we have a Workbook
event provider we can pick from the left-hand side code pane drop-down, listing the available events in the right-hand side drop-down – exactly as if there was an implicit WithEvents Workbook
private field in any other class module:


We use the WithEvents
keyword to declare an object variable for which we would like to handle events. The keyword is only legal in class modules (this includes document class modules and userform modules), and the As
clause may only specify a class that exposes public events. Moreover, an auto-instantiated object cannot be declared WithEvents
, so As New
is also illegal.
Events/callbacks are particularly useful to make application components that don’t “see” each other, still be able to talk to each other and interact: the class that’s raising an event doesn’t know if there’s someone answering the call on the other side, even less who that someone is and what they do for a living. They are a tool that can help decoupling components… to an extent: the problem is that in VBA classes, events are essentially considered implementation details – they are not exposed on the class’ default interface, even though they’re Public
: the only class that is allowed to access an event, is the class that defines it – no other class can raise an event, unless we expose a method specifically for that:
Public Event Something()
Public Sub OnSomething()
RaiseEvent Something
End Sub
Public Sub DoSomething()
OnSomething()
'...
End Sub
The Event
and WithEvents
keywords provide language-level assistance with the otherwise relatively complex wiring-up of callbacks. In order to use them with interfaces, we can use an adapter pattern with formalized SomethingEvents
interfaces – like how the Battleship project demonstrates with the GridViewAdapter
and IGridViewEvents
classes.
Thanks, good wrap. You can also allow the creation of a PublicNotCreatable object instance outside of your library by providing factory functions in a public module, in your library (Reference the lib database in your consumer database).
Public Function NewMyLibObject() As MyLibObject
Set NewMyLibObject= New MyLibObject
End Function
LikeLike
[…] a public Property Set member, we allow code written against the class’ default interface to inject a dependency at the class/instance […]
LikeLike
> There are several ways objects can come into existence in VBA.
You actually missed an option here. You can create light weight objects with CopyMemory/GetMem4… There are a lot of examples of this on vbforums but here’s one example describing implementation of IEnumVARIANT:
http://www.vbforums.com/showthread.php?854963-VB6-IEnumVARIANT-For-Each-support-without-a-typelib
LikeLike
Sure, but that’s Win32 / lower-level than language mechanisms described in MS-VBAL specifications, and such objects are essentially under-the-radar as far as VBA/VB6 is concerned =)
LikeLike
Oh Sorry, I was just thinking because you started talking about COM that it might be relevant 😛
P.S. An interesting article may be “What is Debug” – Because I’m fairly certain it isn’t an object. E.G. ObjPtr(Debug) resolves in an error. Not sure if anyone else has any idea what it might be.
I’ve often wondered if I could cause a break point in another area of the stack (like Debug.Assert causes a break in execution, and how a DLL call which resolves in an error will break on the last line of vba code instead of ‘within the binary module’). Tangent aside I tried looking for an object reference for debug, interfaces for debug, anything… And found nothing. I didn’t even find it in `MS-VBAL`
It might also be worth noting in this article that you can implement IEnumVARIANT without low level APIs (i.e. by piggy baclomg off of the _NewEnum of the collection class.)
LikeLike
OK, but why are we talking about class modules at all? I thought OOP has now been proven to be practically inferior because of their mutability? I’m no mathematician. But I know that in 25 years of VBA, I’ve never seemed to need a class module.
LikeLike
> I thought OOP has now been proven to be practically inferior because of their mutability?
Nope. A handful of Functional Programming zealots would have you believe it, and if you drank the kool-aid then more power to you. Truth is, a paradigm is a tool, and you’d be a fool to claim your hammer is the end-all-be-all ultimate tool, just as much as #TeamShovel and #TeamScrewdriver would be to do the same.
This is about learning how things work, not about deifying a paradigm. VBA can do plain procedural and OOP (yes it can, and very well at that), and has little to no FP capabilities, and I’m not going to write about procedural code, because why would I blog about procedural programming when I can cover material VBA devs with 25 years of experience can learn from?
I write about class modules because class modules are misunderstood and their capabilities seldom explored. And then experienced VBA devs take on slightly more complex endeavors and because there’s little to no material about OOP in VBA, you get “smart UI” apps with tightly coupled logic, and all the problems that come with it.
Knowledge is power. Know your tools.
LikeLiked by 2 people
Rubberduck, Thank you for this conversation. Where can I learn about the problems you mention and how to avoid them with OOP in VBA?
LikeLike
I wrote about “Smart UI” vs Model-View-Presenter here: https://rubberduckvba.wordpress.com/2017/10/25/userform1-show/
There’s a follow-up article too, and then I also wrote about Model-View-Controller in another series where I implement a game of Battleship with a decoupled architecture, part 1 is here: https://rubberduckvba.wordpress.com/2018/08/28/oop-battleship-part-1-the-patterns/
That said OOP principles are very much language-agnostic, so don’t confine your research to VBA resources. The decoupling and testability advantages of SOLID/OOP rely on polymorphism much more than inheritance; the former being unsupported in VBA is not a showstopper at all.
LikeLike
Thank you!
LikeLike
I appreciate that we all love Object models (Excel Application etc). I’m not talking about that. I’m talking about why use a Class Module over a regular Module.
LikeLiked by 1 person