Putting Property Procedures to Work for You

Visual Basic provides three kinds of property procedures, as described in the following table.

Procedure Purpose
Property Get Returns the value of a property.
Property Let Sets the value of a property.
Property Set Sets the value of an object property (that is, a property that contains a reference to an object).

As you can see from the table, each of these property procedures has a particular role to play in defining a property. The typical property will be made up of a pair of property procedures: A Property Get to retrieve the property value, and a Property Let or Property Set to assign a new value.

These roles can overlap in some cases. The reason there are two kinds of property procedures for assigning a value is that Visual Basic has a special syntax for assigning object references to object variables:

Dim wdg As Widget
Set wdg = New Widget

The rule is simple: Visual Basic calls Property Set if the Set statement is used, and Property Let if it is not.

Tip   To keep Property Let and Property Set straight, hearken back to the Basics of yore, when instead of x = 4 you had to type Let x = 4 (syntax supported by Visual Basic to this very day). Visual Basic always calls the property procedure that corresponds to the type of assignment — Property Let for Let x = 4, and Property Set for Set c1 = New Class1 (that is, object properties).

For More Information   "Working with Objects" in "Programming Fundamentals" explains the use of the Set statement with object variables.

Read-Write Properties

The following code fragment shows a typical read-write property:

' Private storage for property value.
Private mintNumberOfTeeth As Integer

Public Property Get NumberOfTeeth() As Integer
   NumberOfTeeth = mintNumberOfTeeth
End Property

Public Property Let NumberOfTeeth(ByVal NewValue _
   As Integer)
   ' (Code to validate property value omitted.)
   mintNumberOfTeeth = NewValue
End Property

The name of the private variable that stores the property value is made up of a scope prefix (m) that identifies it as a module-level variable; a type prefix (int); and a name (NumberOfTeeth). Using the same name as the property serves as a reminder that the variable and the property are related.

As you've no doubt noticed, here and in earlier examples, the names of the property procedures that make up a read-write property must be the same.

Note   Property procedures are public by default, so if you omit the Public keyword, they will still be public. If for some reason you want a property to be private (that is, accessible only from within the object), you must declare it with the Private keyword. It's good practice to use the Public keyword, even though it isn't required, because it makes your intentions clear.

Property Procedures at Work and Play

It's instructive to step through some property procedure code. Open a new Standard Exe project and add a class module, using the Project menu. Copy the code for the NumberOfTeeth property, shown above, into Class1.

Switch to Form1, and add the following code to the Load event:

Private Sub Form_Load()
   Dim c1 As Class1
   Set c1 = New Class1
   ' Assign a new property value.
   c1.NumberOfTeeth = 42
   ' Display the property value.
   MsgBox c1.NumberOfTeeth
End Sub

Press F8 to step through the code one line at a time. Notice that when the property value is assigned, you step into the Property Let, and when it's retrieved, you step into the Property Get. You may find it useful to duplicate this exercise with other combinations of property procedures.

Arguments of Paired Property Procedures Must Match

The property procedure examples you've seen so far have been simple, as they will be for most properties. However, property procedures can have multiple arguments — and even optional arguments. Multiple arguments are useful for properties that act like arrays, as discussed below.

When you use multiple arguments, the arguments of a pair of property procedures must match. The following table demonstrates the requirements for arguments in property procedure declarations.

Procedure Declaration syntax
Property Get Property Get propertyname(1,..., n) As type
Property Let Property Let propertyname(1,..., n, n+1)
Property Set Property Set propertyname(1,..., n, n+1)

The first argument through the second-to-last argument (1,..., n) must share the same names and data types in all Property procedures with the same name. As with other procedure types, all of the required parameters in this list must precede the first optional parameter.

You've probably noticed that a Property Get procedure declaration takes one less argument than the related Property Let or Property Set. The data type of the Property Get procedure must be the same as the data type of the last argument (n+1) in the related Property Let or Property Set.

For example, consider this Property Let declaration, for a property that acts like a two-dimensional array:

Public Property Let Things(ByVal X As Integer, _
ByVal Y As Integer, ByVal Thing As Variant)
   ' (Code to assign array element omitted.)
End Property

The Property Get declaration must use arguments with the same name and data type as the arguments in the Property Let procedure:

Public Property Get Things(ByVal X As Integer, _
ByVal Y As Integer) As Variant
   ' (Code for retrieval from array omitted.)
End Property

The data type of the final argument in a Property Set declaration must be either an object type or a Variant.

Matching Up the Arguments

The reason for these argument matching rules is illustrated in Figure 9.8, which shows how Visual Basic matches up the parts of the assignment statement with the arguments of a Property Let.

Figure 9.8   Calling a Property Let procedure

The most common use for property procedures with multiple arguments is to create property arrays.

Read-Only Properties

To create a read-only property, simply omit the Property Let or (for object properties) the Property Set.

Object Properties

If you're creating a read-write object property, you use a Property Get and a Property Set, as here:

Private mwdgWidget As Widget

Public Property Get Widget() As Widget
   ' The Set statement must be used to return an
   ' object reference.
   Set Widget = mwdgWidget
End Property

Public Property Set Widget(ByVal NewWidget As Widget)
   Set mwdgWidget = NewWidget
End Property

Variant Properties

Read-write properties of the Variant data type are the most complicated. They use all three property procedure types, as shown here:

Private mvntAnything As Variant

Public Property Get Anything() As Variant
   ' The Set statement is used only when the Anything
   ' property contains an object reference.
   If IsObject(mvntAnything) Then
      Set Anything = mvntAnything
   Else
      Anything = mvntAnything
   End If
End Property

Public Property Let Anything(ByVal NewValue As Variant)
   ' (Validation code omitted.)
   mvntAnything = NewWidget
End Property

Public Property Set Anything(ByVal NewValue As Variant)
   ' (Validation code omitted.)
   Set mvntAnything = NewWidget
End Property

The Property Set and Property Let are straightforward, as they're always called in the correct circumstances. However, the Property Get must handle both of the following cases:

strSomeString = objvar1.Anything
Set objvar2 = objvar1.Anything

In the first case, the Anything property contains a string, which is being assigned to a String variable. In the second, Anything contains an object reference, which is being assigned to an object variable.

The Property Get can be coded to handle these cases, by using the IsObject function to test the private Variant before returning the value.

Of course, if the first line of code is called when Anything contains an object reference, an error will occur, but that's not Property Get's problem — that's a problem with using Variant properties.

Write-Once Properties

There are many possible combinations of property procedures. All of them are valid, but some are relatively uncommon, like write-only properties (only a Property Let, no Property Get). And some depend on factors other than the kinds of property procedures you combine.

For example, when you organize the objects in your program by creating an object model, as described in "Object Models" later in this chapter, you may want an object to be able to refer back to the object that contains it. You can do this by implementing a Parent property.

You need to set this Parent property when the object is created, but thereafter you may want to prevent it from being changed — accidentally or on purpose. The following example shows how the Account object might implement a Parent property that points to the Department object that contains the account.

' Private data storage for Parent property.
Private mdeptParent As Department

Property Get Parent() As Department
   ' Use the Set statement for object references.
   Set Parent = mdeptParent
End Property

' The property value can only be set once.
Public Property Set Parent(ByVal NewParent _
As Department)
   If deptParent Is Nothing Then
      ' Assign the initial value.
      Set mdeptParent = NewParent
   Else
      Err.Raise Number:=vbObjectError + 32144, _
      Description:="Parent property is read-only"
   End If
End Property

When you access the parent of an Account object, for example by coding strX = acctNew.Parent.Name to get the department name, the Property Get is invoked to return the reference to the parent object.

The Property Set in this example is coded so that the Parent property can be set only once. For example, when the Department object creates a new account, it might execute the code Set acctNew.Parent = Me to set the property. Thereafter the property is read-only.

For More Information   Because forms in Visual Basic are classes, you can add custom properties to forms. See "Customizing Form Classes" earlier in this chapter.