Modern VBA Best Practices: Default Members

Today I learned that VB.NET does in fact support Default properties. For years I was under the impression that dismissing the Set keyword meant default members couldn’t possibly exist in .NET, and I was wrong: dismissing the Set keyword meant that parameterless default members couldn’t exist in .NET, but VB.NET can still implicitly invoke a Public Property Get Item(index) default member, just like its VB6 ancestor.

Rewind to their inception, and default members/properties have all the looks of a language feature that’s considered a nice convenient way to type code faster (in 20/20 hindsight, that was at the cost of readability). That’s why and how Debug.Print Application can compile, run, and output Microsoft Excel in the debug pane; it’s why and how an ADODB.Connection object and its ConnectionString properties can be impossible to tell apart… as a convenience; how a Range “is” its value(s), a TextBox “is” its text, or an OptionButton “is” True or False.

These are the modern-day considerations for VB.NET default properties (emphasis mine, .NET-specifics removed):

Default properties can result in a small reduction in source code-characters, but they can make your code more difficult to read. If the calling code is not familiar with your class […], when it makes a reference to the class […] name it cannot be certain whether that reference accesses the class […] itself, or a default property. This can lead to compiler errors or subtle run-time logic errors. […]
Because of these disadvantages, you should consider not defining default properties. For code readability, you should also consider always referring to all properties explicitly, even default properties.

I cannot think of a single valid reason for any of these considerations to not be applicable to modern VBA, or even VB6 code. VB.NET removed the need for a disambiguating Set keyword by making a parameterless default member throw a compiler error. For contrast consider this code, and imagine the Set keyword doesn’t exist:

Dim things(9)
things(0) = New Thing

If the Thing class defines a parameterless default member, then who can tell what’s at index 0 of the things array? A Thing object reference? A SomethingElse object reference? The String representation of a Thing instance? 42?

Default members are hopefully not side-effecting magic invisible stardust code that is by definition invoked implicitly, by code that says one thing and does another, and requires looking up the documentation or the object browser definition of a type to remember what member we’re actually invoking – and even then, it can be obscured; the Excel type library is a prime example, with a hidden _Default property being the (drumroll) default property of the Range class, for example. Lastly, an implicit default member call is not 100% equivalent to an explicit one, and that tiny little difference can go as far as instantly crashing Excel.

Sounds terrible. Why would Rubberduck have a @DefaultMember annotation then?

With Rubberduck’s annotation and inspection/quick-fix system, you can easily define default members for your class modules; simply decorate the procedure with a '@DefaultMember annotation, synchronize member attributes, and done.

It’s not because you can, that you should. If you’re like me and someone gave you a knife, you’d probably at least try not to cut yourself. If you’re writing a custom collection class and you want it to be usable with the classic things(i) syntax rather than an explicit things.Item(i) member call, Rubberduck’s job is to help you do exactly that without needing to remove/export the code file, tweak it manually in Notepad++, then re-import it back into the project – that’s why the @DefaultMember annotation exists: because for the rare cases where you do want a default member, your ducky doesn’t let you down.

Currently, Rubberduck won’t complain if you make a parameterless procedure a default member. There’s an inspection idea that’s up-for-grabs to flag them though, if you’re looking for a fun contribution to an open-source project!

5 thoughts on “Modern VBA Best Practices: Default Members”

  1. Good article and interesting to see that VB.NET differentiates between parameterised default members and those without. I wonder whether you have any thoughts on using default members for classes which only have a single field; they encapsulate a single piece of data accessible by a .Value property Let/Get?

    I feel like default members are useful when there is only a single thing that differentiates two classes; for collections it is the items they hold so this makes for a good default member. Ranges meanwhile have a Value, but also an address and a parent sheet etc. – you could not fully define a new Range object from the default Value of another, but you can fully define an ArrayList based on the items (and indices) of another.

    Similarly for types that only encapsulate a single field (both dumb ones like MSForms.ReturnBoolean which have a Value and nothing else, or more OOP classes like a custom Float type that has .CompareTo and .Add methods but wraps a double and nothing else), you could create a new indistinguishable version of the class using only its encapsulated data. If a class constructor only requires a single parameter (value, text, array of stuff), then I feel the field which holds that data could justifiably be given a default member property accessor without compromising readability or making anything too ambiguous, but I’d like to hear your thoughts on the topic.

    Once again thanks for the article, always interesting reads!

    Liked by 1 person

    1. Thanks! I have to side with the official Microsoft recommendations here, and prefer explicit member calls everywhere, even calls to default members – including and not limited to Range.[_Default], ReturnBoolean.Value, etc.; the readability hit (say one thing, do another) isn’t worth the spared handful of keystrokes IMO.

      Like

  2. Somewhat related, but if I had a loosely related question about the VBA SDK–you guys seem to be on top of VBA these days–where or who would I ask to get the best answer?

    Asking VBA SDK questions on StackOverflow seems to be like shouting into a void… so I assume everyone must be hiding out somewhere!

    Like

    1. Did you ask on SO? I, as well as the entire Rubberduck crew (core devs at least), monitor the VBA tag rather closely on SO – I/we might have missed it though (I don’t get to see the dev chat’s ticker feed on my phone)… in which case just drop a link to it under any of my post; I’ll get the ping and post it in the dev chat.
      SO *should* be the single best place to ask about it – assuming you’re facing a specific coding issue: note that broad, whiteboard design stage and/or “how do I xyz?” questions tend to be poorly received, as SO isn’t a good platform for such posts – chat makes a much better medium for these discussions. Do you have 20 rep on Stack Exchange? Join us in the “VBA Rubberducking” chat room on Code Review Stack Exchange (that’s https://chat.stackexchange.com/rooms/14929). If you don’t have the 20 rep, open an issue on Rubberduck’s GitHub repository, one of our devs has moderator privs across SE chat, and can grant you explicit write access if needed.

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s