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
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
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
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!