Collections

For the most part, the methods and properties you expose through an IDispatch interface are entirely your design. The OLE Programmer's Reference describes some guidelines for objects such as those representing an application or a document, which we'll see later in this chapter in Table 14-4, under "Automation Object Hierarchy Design." While that section primarily covers design guidelines, there is one other standard for what are called collections or collection objects—objects that allow a controller to work with a set of similar things as a single group instead of as independent entities.

The simplest example of a collection is the group of open documents in an application. Although each document might have a document object to represent it alone, it would be grouped within a documents collection. If you are familiar with the Multiple Document Interface (MDI) for managing document windows, think of the MDI client window as the collection that holds all the MDI document windows. Collections, however, are not exclusively about documents: a document itself may have a pages collection that groups all the individual pages of a document. In the Patron sample, for example, the CPages class is essentially a collection of CPage objects. (These are C++ objects, mind you, but the concept still holds.) Each CPage is also a collection of CTenant objects.

A collection object itself is an automation object, with its own IDispatch interface and its own methods and properties. Usually it will have Add and Remove methods, as you would expect for managing any group of things. For example, a documents collection generally includes methods such as Add (File New) and Open (File Open) to create or open a document. Collections have a few stringent requirements, however, that allow controllers to iterate over the elements in the collection. The first is a standard property named Count, which returns a long describing the number of elements in the collection. The second is a standard method named Item. This takes an index4 as an argument and returns any type desired, whatever is appropriate for the element of that index in the collection. So, for example, if you have a collection of integers, you can perform a summation on that collection in Visual Basic with code such as the following:


sum = 0
for I = 1 to NumberSet.Count
sum = sum + NumberSet.Item(I)
next I

Here NumberSet is the collection itself. If you have a collection of documents in which each document is an automation object with a Visible property, you can hide all the document windows in that collection with the same construct:


for I = 1 to DocumentCollection.Count
set doc = DocumentCollection.Item(I)
doc.Visible = False
next I

The problem with a construct such as this is that the collection itself may not necessarily support an index to the Item method, in which case the loop would fail. In addition, it is not necessarily true that all collections have items stored with sequential index values or that an item of a particular index will always have that same index. The unpredictability of an item's position is what distinguishes a collection from a simple array.

Visual Basic for Applications (VBA) and later versions of Visual Basic and other controllers support a collection without indexes through the foreach loop, which makes iteration of this sort more concise:5


for each doc in DocumentCollection
doc.Visible = False
next doc

The foreach construct doesn't require any intermediate variables. It also doesn't require that the controller know the number of elements in the collection or depend on the collection's support for an item index.

To support iteration over the collection in this manner, a collection object must support a third standard method (or property) named _NewEnum (DISPID_NEWENUM). The leading underscore, according to OLE Automation, means that the method or property is hidden and should not be shown in any user interface that a controller might present. _NewEnum takes no arguments (which is why it can be a property or a method) and returns an IUnknown pointer. But a pointer to what object? This return pointer does not give access to the collection object, but instead it points to an enumerator object with the interface IEnumVARIANT.6 As we know from other enumerator interfaces we've seen in earlier chapters, this interface is concerned with enumerating an array—or group—of VARIANT structures that can contain just about any kind of data, from an integer to a character string to even other objects referenced through their IUnknown or IDispatch pointers—exactly what we want to support an iteration construct. We'll see more details about this in the next section.

Any foreach language construct will call _NewEnum internally to obtain the IEnumVARIANT pointer and then call IEnumVARIANT::Next to access the next element in the collection. It applies whatever code is in the body of the foreach statement to that element—for example, using a number in an expression or invoking another object's properties and methods—and continues looping until IEnumVARIANT::Next returns an error. When the loop is complete, Visual Basic calls IEnumVARIANT::Release.

The final standard for collections is naming: the name of a collection object should be the plural of the objects it collects. For example, a collection of Word objects should be called the Words collection; a collection of Vertex objects should be the Vertices collection; a collection of Mouse objects should be the Mice collection. So what if you have Moose objects? Append "Collection," as in MooseCollection.

4 The index can be zero-based or one-based, depending on what sort of controllers you might target. A C/C++ controller prefers zero-based indexes; a Basic controller prefers one-based indexes. The choice is, unfortunately, left up to the object implementer. No standard exists at the time of writing.

5 DispTest and Visual Basic 3.0 do not support the for…each construct. To do the same operation from these controllers, you must use the more traditional approach to iteration as described earlier. This limitation is strictly a feature deficiency of these specific controllers and should in no way deter you from implementing a proper collection object and a proper enumerator.

6 Older versions of the OLE Programmer's Reference are confusing about who implements what interface. The collection object itself implements IDispatch (which includes IUnknown) as well as any other interfaces it wants. However, _NewEnum returns the IUnknown pointer for a separate