Visual Basic Concepts
Dealing with Circular References
Containment relationships allow you to navigate through a hierarchy from a high-level object to any of the objects it contains.
Object models that strictly express containment are like trees. Any given branch (object) may divide into smaller branches (dependent objects), but the smaller branches do not loop around and rejoin the trunk or lower branches.
Object models with loops, or circular references, result when a dependent object has a property or variable that holds a reference to one of the objects that contains it.
For example, an Order object might have a Contact property that contains a reference to a Contact object, representing the individual who placed the order. The Contact object might in turn have a Company property that contains a reference to a Company object.
Up to this point, the hierarchy is a tree. However, if the Company object has a MostRecentOrder property that contains a reference to the Order object, a circular reference has been created.
*Note* You could avoid the circular reference in this case by making the MostRecentOrder property a text key that could be used to retrieve the Order object from the component’s Orders collection.
Circular References in Visual Basic Components
Consider the simplest form of circular reference, a Parent property. The Parent property of a dependent object contains a reference to the object that contains it.
For example, in the Microsoft Excel object model, a Button object is contained by a Worksheet object. If you have a reference to a Button object, you can print the name of the Worksheet that contains it using code like the following:
' If the variable btnCurrent contains a reference to a ' Microsoft Excel Button object, the following line of ' code displays the Name property of the Worksheet ' object that contains the button. MsgBox btnCurrent.Parent.Name
Microsoft Excel is written using C++ and low level COM interfaces, and it maintains the Parent properties of its objects without creating circular references. If you implement such a relationship in Visual Basic, you have to take into account the way Visual Basic handles the creation and destruction of objects.
Visual Basic destroys an object when there are no longer any references to it. If an object’s parent has a collection that contains a reference to the object, that’s enough to keep the object from being destroyed. In the same way, an object continues to exist if the parent has an object property that contains a reference to the object.
When a parent object is destroyed, the variables that implement its properties go out of scope, and the object references are released. This allows the dependent objects to terminate. If a dependent object has a Parent property, however, Visual Basic cannot destroy the parent object in the first place, because the dependent object has a reference to it.
The dependent object cannot be destroyed, either, because the parent has a reference to it. This situation is illustrated for an out-of-process component in Figure 6.7.
Figure 6.7 Circular reference prevents objects from being destroyed
Client application B has released its reference to its Widget object. The Widget object has a reference to a Knob object, whose Parent property refers back to the Widget object, keeping both objects from terminating.
A similar problem occurs if the Widget object contains a collection of Knob objects, instead of a single Knob. The Widget object keeps a reference to the Knobs collection object, which contains a reference to each Knob. The Parent property of each Knob contains a reference to the Widget, forming a loop that keeps the Widget object, Knobs collection, and Knob object alive.
The objects client B was using will not be destroyed until the component closes. For example, if client A releases its Widget object, there will be no external references to the component. If the component does not have any forms loaded, and there is no code executing in any procedure, then the component will unload, and the Terminate events for all the objects will be executed. However, in the meantime, large numbers of orphaned objects may continue to exist, taking up memory.
*Note* If a circular reference exists between objects in two out-of-process components, the components will never terminate.
Circular References and In-Process Components
If you implement your component as a DLL, so that it runs in the process of the client application, it’s even more important to avoid circular references. Because an in-process component shares the process space of the client application, there is no distinction between ‘external’ and ‘internal’ references to a public object. As long as there’s a reference to an object provided by the component, it stays loaded.
This means that a circular reference keeps an in-process component loaded indefinitely, and the memory taken up by orphaned objects cannot be reclaimed until the client application closes.
*For More Information* See "ActiveX Component Standards and Guidelines" for more information on object models.