Those readers familiar with the use of objects in VB(A) know about the three Property procedures (Get, Let, and Set). The typical use of these procedures is to create a property in the class. The consumer of the class can then interrogate or assign a value to the property. What most people may not be aware of is that one can use property procedures in standard modules! As in the case of a class module, a property behaves like a variable but in a far more complex manner.
The major advantage of a property over a public variable is that one can do a lot more than provide the consumer of the variable unfettered access to the raw value. For example, suppose one wants to create a variable that holds one of the three primary computer colors – red, green, or blue – and no other color. In a standard module named ColorManager one could create
Option Explicit
Option Private Module
Public lPrimaryColor As Long 'only legitimate values are R, G, or B
The major downside of this approach is that there is no way to enforce that lPrimaryColor contains only of the three legitimate values, RGB(255,0,0), RGB(0,255,0), or RGB(0,0,255). In another module, say, ColorConsumer, code like that below would work just fine even though it violates the intent of lPrimaryColor.
Sub unfetteredVariableAccess()
lPrimaryColor = RGB(128, 0, 0)
MsgBox lPrimaryColor
End Sub
An alternative – and far more robust – approach would be to change the ColorManager standard module to:
Option Explicit
Option Private Module
Dim lPrimaryColor As Long
Property Get PrimaryColor() As Long
PrimaryColor = lPrimaryColor
End Property
Property Let PrimaryColor(uPrimaryColor As Long)
Select Case uPrimaryColor
Case Is = RGB(255, 0, 0), RGB(0, 255, 0), RGB(0, 0, 255):
lPrimaryColor = uPrimaryColor
Case Else:
lPrimaryColor = 0
End Select
End Property
The code above declares a property PrimaryColor with a paired Get and Let procedures. Effectively, and just as in a class module, the use of the two property procedures creates something that others can use as though it were a single variable!
This impacts consumers, such as ColorConsumer, in two related ways. The first is because ColorManager is a private module and lPrimaryColor is no longer a public variable. Consequently, ColorConsumer can no longer directly access the variable. The code in the unfetteredVariableAccess subroutine above would generate a compile error: Variable not declared. The second is that PrimaryColor can now be treated as a variable. Suppose ColorConsumer has the following procedure:
Sub testPropInOtherModule()
PrimaryColor = RGB(255, 0, 0): MsgBox PrimaryColor
PrimaryColor = RGB(0, 255, 0): MsgBox PrimaryColor
PrimaryColor = RGB(0, 0, 255): MsgBox PrimaryColor
PrimaryColor = RGB(128, 0, 0): MsgBox PrimaryColor
End Sub
The above code is perfectly valid and will execute as expected. The last assignment (of a non-primary color to the PrimaryColor changes it to black, i.e., RGB(0,0,0).
Since the property behaves like a variable of a particular data type, one can use it just one would use a variable of that data type. Since the PrimaryColor property is defined as a long (see the Get procedure), one can use it as though it were a simple variable of type Long as in the example below. The rotatePrimaryColor subroutine rotates the PrimaryColor property through its three legitimate values first with a complex IF statement and then nested IIf functions. In each instance, an uninitialized property becomes red.
Sub rotatePrimaryColor()
If PrimaryColor = RGB(255, 0, 0) Then
PrimaryColor = RGB(0, 255, 0)
ElseIf PrimaryColor = RGB(0, 255, 0) Then
PrimaryColor = RGB(0, 0, 255)
Else
PrimaryColor = RGB(255, 0, 0)
End If
MsgBox PrimaryColor
PrimaryColor = IIf( _
PrimaryColor = RGB(255, 0, 0), RGB(0, 255, 0), _
IIf(PrimaryColor = RGB(0, 255, 0), RGB(0, 0, 255), _
RGB(255, 0, 0)))
MsgBox PrimaryColor
End Sub
The use of the property PrimaryColor can – and should – be extended to the ColorManager module itself. The developer of the ColorManager module should exhibit the same discipline that the developer of a class module must exhibit and resist the temptation to directly access variables associated with properties. One can easily make the rotatePrimaryColor subroutine above a public sub in the ColorManager module (essentially the equivalent of a method of a class). Of course, now that the subroutine is in the same module as the lPrimaryColor variable, it would have direct access to the variable. However, basic defensive programming requires that the developer limit herself to the PrimaryColor property. Put the code below in the ColorManager module
Public Sub rotatePrimaryColor()
PrimaryColor = IIf( _
PrimaryColor = RGB(255, 0, 0), RGB(0, 255, 0), _
IIf(PrimaryColor = RGB(0, 255, 0), RGB(0, 0, 255), _
RGB(255, 0, 0)))
End
Sub
Now, in the consumer module, ColorConsumer, one could have code like:
Sub testRotateColors()
rotatePrimaryColor
MsgBox PrimaryColor
rotatePrimaryColor
MsgBox PrimaryColor
rotatePrimaryColor
MsgBox PrimaryColor
rotatePrimaryColor
MsgBox PrimaryColor
End Sub
This tip demonstrated that the use of a property is not restricted to a class module. The same developer advantages enjoyed by the use of a property in a class module also apply to its use in a standard module.