Namespaces in VBA

VBA may not have the same level of formal support for namespaces as VB.NET, but with a little creativity, we can realize the same benefits in other ways.

Namespaces in VBA

Some programming languages, such as VB.NET, use namespaces extensively to organize code.

Namespaces organize the objects defined in an assembly. Assemblies can contain multiple namespaces, which can in turn contain other namespaces. Namespaces prevent ambiguity and simplify references when using large groups of objects such as class libraries.

.NET Framework namespaces address a problem sometimes called namespace pollution, in which the developer of a class library is hampered by the use of similar names in another library. These conflicts with existing components are sometimes called name collisions.

Namespaces are nowhere near as prominent in VBA as they are in VB.NET, but they do exist.  For example, you can fully qualify calls to routines in standard modules by prefixing the routine names with the module name.  This allows you to have two public routines with the exact same name as long as they reside in different modules.  

VBA does not have the same level of explicit support for namespaces as VB.NET:

  • There is no equivalent Imports keyword
  • You cannot nest namespaces to create namespace hierarchies

However, with a bit of creativity, we can simulate these features within the confines of VBA to achieve the primary benefit of namespaces in VB.NET: a reduction in namespace pollution.

Namespace Semantics in VBA

As a starting point, let's refer to the VBA language specification to see how the language itself supports namespaces:

Static semantics. Simple name expressions are resolved and classified by matching <name> against a set of namespace tiers in order.

The first tier where the name value of <name> matches the name value of at least one element of the tier is the selected tier. The match that the simple name expression references is chosen as follows:

• If the selected tier contains matches from multiple referenced projects, the matches from the project that has the highest reference precedence are retained and all others are discarded.
• If both an Enum type match and an Enum member match are found within the selected tier, the match that is defined later in the module is discarded. In the case where an Enum member match is defined within the body of an Enum type match, the Enum member match is considered to be defined later in the module.
• If there is a single match remaining in the selected tier, that match is chosen.
• If there are 2 or more matches remaining in the selected tier, the simple name expression is invalid.

If all tiers have no matches, unless otherwise specified, the simple name expression is invalid.

Here are the namespace tiers referenced above in order of precedence:

The namespace tiers under the default binding context are as follows, in order of precedence:

Procedure namespace: A local variable, reference parameter binding or constant whose implicit or explicit definition precedes this expression in an enclosing procedure.
Enclosing Module namespace: A variable, constant, Enum type, Enum member, property, function or subroutine defined at the module-level in the enclosing module.
Enclosing Project namespace: The enclosing project itself, a referenced project, or a procedural module contained in the enclosing project.
Other Procedural Module in Enclosing Project namespace: An accessible variable, constant, Enum type, Enum member, property, function or subroutine defined in a procedural module within the enclosing project other than the enclosing module.
Referenced Project namespace: An accessible procedural module contained in a referenced project.
Module in Referenced Project namespace: An accessible variable, constant, Enum type, Enum member, property, function or subroutine defined in a procedural module or as a member of the default instance of a global class module within a referenced project.

Optional Namespaces*

When I say "Optional Namespaces," I'm referring to the fact that VBA allows you to fully qualify a procedure call by prefixing it with the name of the module or library it belongs to.

In practice, I use this most often when I want to refer to a procedure or routine from the standard VBA library that I have overridden with a custom procedure of the same name.  For example, I sometimes write a custom Round function that uses arithmetic rounding rather than banker's rounding.  If I want to refer to the original VBA Round function, I can do that using VBA.Round.  

Another example is my Unicode-friendly MsgBox drop-in replacement function.  My custom function is named MsgBox as it is meant to completely replace the existing MsgBox functionality.  However, if I wanted to call the original MsgBox function for whatever reason, I could still do so by fully qualifying it with the name of the library it belongs to: VBA.MsgBox.

"Optional namespaces" allow you to disambiguate calls to procedures with the same name.  

Required Namespaces*

What if you want to require that your procedure calls include a namespace prefix?

You can do that by taking advantage of a hidden feature of VBA class modules: the PredeclaredID attribute.  If you're not familiar with the technique, I described two methods for setting the attribute in a previous article: HOW-TO: Setting the PredeclaredID Attribute.

While "optional namespaces" allow you to disambiguate calls to identical procedure names, "required namespaces" help reduce namespace pollution in the first place.

Namespace Hierarchies

Finally, we can create "namespace hierarchies" through the use of multiple class modules.  

The top level class module would have its PredeclaredID attribute set to True so that it could be called without having to explicitly create a new object.  Within that top level class module, you would include one or more public properties that refer to other class modules.  Those other class modules could in turn refer to yet more class modules.

You could even simulate the Imports [ aliasname = ] namespace.element construct from VB.NET.

Instead of this example VB.NET statement...

Imports dirinf = System.IO.DirectoryInfo

...you could write the following VBA...

Dim dirinf As DirectoryInfo
Set dirinf = System.IO.DirectoryInfo

Of course, there is no built-in System library in VBA like there is VB.NET.  The VBA example above assumes that you have the following three class modules in your application:

  • System: with PredeclaredID = True and a Public Property named IO (of type IO)
  • IO: with a Public Property named DirectoryInfo (of type DirectoryInfo)
  • DirectoryInfo: a class module with methods and properties related to directory information

* NOTE: The terms "Optional Namespaces" and "Required Namespaces" are terms that I am using for convenience only.  To my knowledge, they are not technical terms.  And, even if they are, they may have precise meanings that are different than how I am using them here.

External references

[MS-VBAL]: Simple Name Expressions
A simple name expression consists of a single identifier with no qualification or argument list. simple-name-expression = name Static

Image by WikiImages from Pixabay

All original code samples by Mike Wolfe are licensed under CC BY 4.0