Sunday, March 20, 2011

Coding a Default Property


The concept of a "default property" for an object isn't what it used to be. Programmers new to VB.NET after 2001 (When VB6 was taken behind the barn and shot.) may not even be aware of how it used to be. In brief, you used to be able to write code like this:

TextBox1 = "Text"
This was really shorthand for ...

TextBox1.Text = "Text"
... because the Text property was the default property.
VB.NET continues to include a "Default" property attribute, but it's not at all the same. In fact, the way it's really used is documented in such an obscure way at Microsoft that there is a real need for some clarification.
The Microsoft documentation for declaring a Default Property in a Class definition is fairly straightforward. But you have to read the whole thing. The initial step-by-step instructions don't work. You could get the idea that all you have to do is simply use the Default keyword instead of Private, Public, or Shared.

Default Property myProperty(ByVal index As Integer) As String
There are a few more rules.
  1. The new VB 2010 "auto implemented" property syntax doesn't work. In other words, you also need a Get and Set and an End Property. (But, Microsoft doesn't make this part of their documentation.)
  2. You have to use at least one parameter for the property. (There's more to this rule, too.)
You might think that you can just write this code:

' Code in a calling procedure
Dim theInstance As New aClassExample
MsgBox(theInstance)

Public Class aClassExample
Private m_myProperty As String = "Red"
Default Property myProperty(
ByVal theDefaultArg As Integer
) As String
Get
myProperty = m_myProperty
End Get
Set(ByVal value As String)
m_myProperty = value
End Set
End Property
End Class
No. An ArgumentException is thrown. It's fairly obvious to see that an argument must be passed, but the language of the exception message is curious:

In order to evaluate an indexed property, the property must be qualified and the arguments must be explicitly supplied by the user.
So, this isn't just a default property, it's also an indexed property. That's something that the documentation doesn't make clear. The code that does work simply adds the parameter to the instance in the calling procedure:

MsgBox(theInstance(1))
What does the "1" parameter do in this code? Absolutely nothing. But it still has to be there. In other words, there is no way to write a default property that is not indexed.
The right way to think of a "default" property in VB.NET is to substitute the word "indexed" for "default". In VB.NET, you can create a property that makes it simpler to define indexes to a collection. Microsoft just used the keyword "Default" for this type of property. Many sources, in fact, refer to a VB.NET default property as an indexer because that's what it does.
Here's an example that does it right. This code declares an indexer in the class DefaultPropClass. This indexer is capable of returning a member of an array that is maintained in an instance of the class by either an index number or a string that matches an element of the array.

Public Class DefaultPropClass
Private m_theProperty As List(Of theStructure)
Public Sub New()
m_theProperty = New List(Of theStructure)
End Sub
' This ReadOnly Property returns the
' value corresponding to an integer index
Default Property theProperty(
ByVal index As Integer) As theStructure
Get
Return m_theProperty(index)
End Get
Set(ByVal value As theStructure)
m_theProperty.Add(value)
End Set
End Property
' This ReadOnly Property returns the
' value corresponding to a string key
Default ReadOnly Property theProperty(
ByVal index As String) As theStructure
Get
For Each p As theStructure
In m_theProperty
If p.theKey = index Then
Return p
End If
Next
Return Nothing
End Get
End Property
End Class
Public Structure theStructure
Dim theKey As String
Dim theValue As String
End Structure
The code that uses this class might look like this:

Dim aCollection As New theStructure
aCollection.theKey = "1"
aCollection.theValue = "George"
Dim aInstance As New DefaultPropClass
aInstance(1) = aCollection
aCollection.theKey = "2"
aCollection.theValue = "Jeremy"
aInstance(2) = aCollection
MsgBox("Integer parameter 1: " &
aInstance(1).theValue)
MsgBox("String parameter 1: " &
aInstance("1").theValue)
The value returned by the integer parameter 1 is "Jeremy" since the collection is zero based. the value returned by the string parameter "1" is "George". You might be asking, "Where is anything defaulted?" One way to answer the question is to note that this code also works:

MsgBox("String parameter 1: " &
aInstance.theProperty("1").theValue)
In other words, by using a default property you can avoid typing the text ".theProperty" in your code. It doesn't seem worth it. This might help explain why Microsoft has made upgrading their documentation on this a (a-hem) secondary priority. In fact, at the very end of one Microsoft page, they're pretty blunt about it:

You should consider not defining default properties. For code readability, you should also consider always referring to all properties explicitly, even default properties.