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!