Object Variables Demystified

[Previous] [Next]

Object variables maintain references to objects. When an object variable goes out of scope, obtains a reference to another object, or is explicitly set to Nothing, the reference count of the object it was referencing decrements by one. The object is destroyed when the reference count of the object reaches zero. Objects in Visual Basic cannot be destroyed in any other way. This actually works in favor of the Visual Basic programmer, because it is impossible for an object variable to have a dangling reference caused by the destruction of the object it was referencing by another process.

An object's constructor is called only when the object is created. (Remember that objects are created by using the keyword New or the CreateObject function.) Here's an example:

 Set myCat = New Cat ' The Cat constructor is called. 

Additional references to an object are automatically assigned to new object variables when an existing object variable with a valid reference to an object is assigned to the new object variable by means of the Set statement. Said another way, the reference count of an object increments as the number of object variables that contain references to it increases:

 ' - Cat object created ' - Constructor called ' _ Reference to Cat object returned to myCat1 ' _ Cat object reference count = 1 Set myCat1 = New Cat ' - Second reference to Cat object obtained by myCat2 ' _ Cat object reference count = 2 Set myCat2 = myCat1 

Because object variables are references, and objects are destroyed only when their reference counts reach zero, careless management of object variables can cause undesired behavior in an application that might be difficult to resolve. Two dilemmas generally arise. First, it can be difficult to control the state of an object, because one client can change the state of an object without another client with a reference to the same object knowing. Second, because objects are deleted only when the last reference is released, circular referencing (referencing an object from another object and vice versa) could cause a deadlock—neither object's reference count will reach zero unless the programmer takes premeditated measures to avoid such a problem. The next section illustrates these dilemmas and the actions you can take to better evade them.

Object References

When more than one object variable has a reference to the same object, both have full rights to affect the state of the object via its public interfaces, as illustrated here:

 Set mySavAcct = New SavingsAccount mySavAcct.Deposit 25 Set anAccount = mySavAcct anAccount.Withdraw 20 MsgBox mySavAcct.Balance ' Message box will show $5.000. 

This example shows clearly what has transpired, but imagine the difficulties in following the actions on an object when the tasks performed by the object variables mySavAcct and anAccount occur in different places.

A less obvious case is one in which you have created a class that has a read-only property. For example, the following code extract illustrates an object variable of type Person that has a read-only property called Name, which returns a string:

 strName = Person.Name ' OK Person.Name = "Spike" ' Error: property Name is read-only. 

However, if the read-only property is of some object type, read-only access is easily violated. If you define a class property of some object type, the property will return a reference to an object based on your implementation, as shown in the following code extract.

 ' Class Foo ' Private m_bars As Collection Private Sub Class_Initialize() Set m_bars = New Collection End Sub  Public Property Get Bars() As Collection Set Bars = m_bars End Property '''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Client Code ' Dim Foo1 As Foo Dim myBars As Collection Set Foo1 = New Foo ' Set a reference in myBars to the Bars ' collection returned from Foo1. Set myBars = Foo1.Bars ' Add Peanut Butter to object variable myBars. myBars.Add "Peanut Butter" ' Based on implementation in class Foo, the read-only ' property Bars is violated. The message box will display ' Peanut Butter. MsgBox Foo1.Bars(1) 

Because the implementation of the read-only property Bars in class Foo returns a reference to a Collection class object that it created privately, the object variable receiving that reference can change the contents of that Collection object. In this case, the read-only designation does not protect the state of the object Foo1. All it does is prevent Foo1's Bars property value from referring to another Collection object:

 ' Compile-time Error: Invalid use of property Set Foo1.Bars = New Collection 

This behavior is inconsistent with intrinsic types. Visual Basic implicitly generates a copy of the property value for intrinsic types such as Long, String, and so forth; therefore, the receiving client can do whatever it pleases with its copy without affecting the property value of the originating object.

Object variables are never implicitly cloned by the Visual Basic compiler. To provide full read-only protection of a property of an object type, you must define your own cloning mechanism. To make the Bars property of the Foo class completely read-only, I have modified the code as follows:

 ' Class Foo ' Private m_bars As Collection Private Sub Class_Initialize() Set m_bars = New Collection End Sub  Public Property Get Bars() As Collection Dim cloneBars As Collection Dim nIndex As Long   ' Clone m_bars collection object. ' ' Create clone collection. Set cloneBars = New Collection   ' Add all items from m_bars collection to clone. For nIndex = 1 To m_bars.Count   cloneBars.Add m_bars(nIndex), nIndex Next nIndex   ' Return clone. Set Bars = cloneBars End Property 

Notice that a new Collection object is created, and a reference to it is stored to the object variable cloneBars. Then, in order to copy the contents of the property value m_bars, the entire collection is traversed, and each item is singly added to the cloneBars Collection object. Depending on the size of the collection, this can be an expensive price to pay for read-only property object values. Hence, you must evaluate the usage of object variables as read-only property values on a case-by-case basis.

Another misleading example of the same magnitude is passing objects by value to a function or subroutine. Here again, you would think the following code would allow you to modify an object passed by value without affecting the state of the object external to the function:

 ' Foo subroutine definition Sub Foo(ByVal theBar As Bar) theBar.Flavor = "Oatmeal Raisin" End Sub ''''''''''''''''''''''''''''''''''' ' Client code ' Dim myBar As Bar Set myBar = New Bar myBar.Flavor = "Chocolate Chip" Foo myBar ' You might think the message box will display ' myBar = Chocolate Chip because the myBar object ' is passed by value. But it will display ' myBar = Oatmeal Raisin because the object variable, ' not the object, is passed by value; hence, it is still ' referring to the same object. ' MsgBox "myBar = " & myBar.Flavor 

Remember, you work with object variables that are references to objects. Passing an object by value to a function does not protect the state of the object external to the function. The reason for this is that the object variable is passing by value, not the object. As a result, the compiler creates a hidden object variable that is assigned a reference to the same object as the passing parameter:

 ' The function typed in by the programmer... Sub Foo(ByVal myBar As Bar) myBar.Flavor = "Oatmeal Raisin" End Sub ' translates to the function generated by the Visual Basic compiler. Sub Foo(ByRef myBar As Bar) Dim HiddenObject As New Bar Set HiddenObject = myBar HiddenObject.Flavor = "Oatmeal Raisin" End Sub 

Visual Basic is creating a new instance of the Bar object, only to have it released when a reference to the Bar object currently held by myBar is assigned to the HiddenObject object variable—and you know the rest. This overhead can be avoided by always passing object variables by reference.

Circular References

Circular references occur when two or more objects refer to one another. Because an object is not destroyed until the last reference to it is released, you can easily have a deadlock in Visual Basic. Keep in mind that it is the deadlock scenario you want to avoid, not circular referencing. In fact, some of the design patterns in this book intentionally use circular references to establish a specific relationship. For example, if you were to create a Manager design pattern, you would want the Manager object to maintain a one-to-many relationship with its Worker objects. This implies that the Manager object can contain a collection of many Worker object references, and each Worker object contains a reference to a single Manager object. Hence, it is clear that in order to have a one-to-many relationship in object-oriented terms, circular referencing might be unavoidable. (See Figure 2-6.) This further implies that in such a relationship, the Manager object cannot be destroyed while its Worker objects still exist. Conversely, Worker objects cannot be destroyed while their Manager object still exists.

To avoid deadlock, you could remove this one-to-many relationship. Doing so would defeat the purpose of this design pattern, however. The only true option is to design the Manager and Worker classes to expose methods that reflect an explicit understanding in their relationship. For example, prior to destroying a given Manager object, all dependent Worker objects must be notified. (Prior to destroying a Worker object, its Manager must be notified.) Notification should be in the form of a method call that results in the release of the object reference held by the object being notified. The object referenced by the notifier will be destroyed once the notifier releases its reference because it should contain the last remaining reference.

Figure 2-6. This class diagram of the Manager design pattern illustrates circular referencing.

In short, circular referencing can be intentional. When this is the case, an architecture must be put in place that permits explicit notifications between objects in this relationship in order to break the circle and avoid deadlock. Deadlock occurs when objects in a relationship contain references to one another, and for that reason cannot be destroyed through standard means. Consequently, objects in this scenario will not be destroyed until the process in which they exist is destroyed. Because Visual Basic objects are based on COM technology, the process sometimes will not destruct until all its objects are destroyed. When this happens, you will have to destroy the process manually. This can be accomplished through the Task Manager in Microsoft Windows NT and Windows 2000 and through the Close Program dialog box (press Ctrl+Alt+Delete) in Microsoft Windows 95 and later. I'll leave it up to you to figure out how these operating system tools function. In fact, if you have noticed in the Task Manager that you have lingering Visual Basic component processes despite having verified that all references to objects the component contains have been released, your component is exhibiting symptoms of deadlock due to circular referencing.

Design patterns rely heavily on object collaboration. Understanding how to use object variables effectively will allow you to better appreciate the design pattern implementation techniques used throughout this book.



Microsoft Visual Basic Design Patterns
Microsoft Visual Basic Design Patterns (Microsoft Professional Series)
ISBN: B00006L567
EAN: N/A
Year: 2000
Pages: 148

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net