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!
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!
LikeLiked by 1 person
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.
LikeLike
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!
LikeLike
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.
LikeLike
[…] one single member of a class can be the class’ default member. Default members should generally be avoided, but they are very useful for indexed Item properties of custom collection classes. This annotation […]
LikeLike