This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.


MIND


This article assumes you're familiar with VBScript
Download the code (3KB)

Using Classes with VBScript 5.0
Eric Lippert

Encapsulation and classes are not new concepts, but developers using VBScript have long gone without. VBScript 5.0 brings classes to the masses.
As computing power has increased, the level of encapsulation available to programmers has also greatly improved. Encapsulation (or information hiding) is the ability to organize programming language constructs (variables, loops, procedures, and so on) so that the programmer can figure out the important characteristics of the program without worrying about its underlying implementation.
      Encapsulating data-manipulating algorithms into functions and subroutines has been, until now, the strongest form of encapsulation available in VBScript. With the release of Visual Basic Scripting Edition (VBScript) version 5.0, programmers who use VBScript can now encapsulate data and the functions that operate on that data into a class. Classes have been available in Visual Basic® for some time now, and VBScript classes are, like all other VBScript features, a simple subset of the Visual Basic syntax. In this article, I'll describe two primary uses for classes: building abstract data types and building objects that ensure data consistency.

Hello, Dave
      Here is a simple example that shows the declaration of a class and the creation of an instance:


 Class Greeter
     Public Sub SayHello(Name)
         MsgBox "Hello, " & Name & ", welcome to " & Store & "."
     End Sub
     Public Store
 End Class

 Dim MyGreeter
 Set MyGreeter = New Greeter
 MyGreeter.Store = "S-Mart"
 MyGreeter.SayHello "Dave"
The class Greeter is declared, then an instance of that class (MyGreeter) is created as an object. The properties and methods of that object can then be set and retrieved with the dot operator—the same way that you'd access members of any other object in VBScript.
      Unlike Visual Basic, VBScript uses the Class…End Class statements to demarcate the contents of the class. Visual Basic uses a separate .cls file to define a class, but since VBScript might be included in a Web page where there is no file system per se, this syntax allows classes to be declared in-place. (Because the VBScript syntax is supposed to be a subset of Visual Basic, the Visual Basic team expects to add this syntax to a future version.)
      Note that the property (Store) and the procedure (SayHello) are declared as Public. Data members and procedure members may also be declared as Private. Private members may only be accessed from within the class, not by any callers of a class instance. Private members allow the class implementer to encapsulate class functionality into procedures and properties that need not be accessed directly by the caller of the class.
      VBScript 5.0 is not strictly an object-oriented language like C++ or Java. There is no notion of polymorphism or inheritance in VBScript 5.0. Though VBScript classes allow you to define your own objects, you cannot define an object hierarchy where, say, a Terrier class is a subclass of Dog, which is a subclass of Mammal. VBScript classes are merely a way to group data and the operations on the data together to improve encapsulation.

A Simple Abstract Data Type
      VBScript provides many primitive data types like strings, integers, floating-point numbers, and dates. It also provides an aggregate data type, the array. There are many operators (+, /, Is, &, and so on) that manipulate these data types. VBScript classes allow you to define your own abstract data types (ADT) by defining a class with member procedures that implement the semantics of your ADT.
      A simple example is the queue ADT. A queue is essentially a list of items where items may only be added to the end and only removed from the front—just like a queue at the movies. Three operations can be performed on a queue: enqueue (add a new item to the end of the queue), dequeue (remove an item from the front of the queue), and length (determine how many items are in the queue).
      Naturally, you might want to put some restrictions on the semantics of these three operations. Length should be a read-only property, for instance. The DeQueue function should produce an error if you attempt it on an empty queue. Furthermore, the internal techniques that you use to implement the queue should be kept secret from other procedures—the caller of the queue should not know or care how the queue works internally, and should not have access to any internal structures needed to maintain the queue. This encapsulation helps ensure that callers of your ADT do not create potential bugs by introducing dependencies on implementation details. VBScript classes allow you to set up private implementations of their internals.
      The queue ADT is implemented in Figure 1 as a doubly linked list, but that fact is invisible to the calling main routine. If the main routine attempted to fetch the value of Q.Count, Q.Head, or Q.Tail, the call would generate a runtime error because those data members are marked as Private in the class. To allow read-only access to the number of items in the queue, a Property Get function called Length is defined. Property accessor functions may take arguments, and may be defined as Get, Set, or Let—you can have any combination of accessors in a class. The code also defines a custom error number that the DeQueue method throws when an empty queue is dequeued.
      You've probably noticed the private Class_Initialize subroutine. VBScript automatically calls this subroutine (if one exists) when an instance of the class is created with the New operator. The logic of the EnQueue and DeQueue operations relies on Count, Head, and Tail being initialized to particular values, so this is a good place to do that initialization. Not shown in Figure 1 is the Class_Terminate method, which is called when an instance of a class is no longer referenced anywhere. Any operations needed for clean up after the class instance can be performed in the Class_Terminate method.

Pick a Card, Any Card
      Now that you have a new abstract data type, let's look at how to use it to implement something a little less abstract. Suppose you were writing a card game. You would need something to represent individual cards as well as aggregate card game objects like decks or hands. A full treatment of even a simple card game is well beyond the scope of this article, but Figure 2 shows you how to use the queue ADT to implement a simple shuffled deck of cards.
      The Card class that's defined first is extremely simple, with just two public data members and a read-only property combining them into one string. The interesting code here is in the Deck class. When the class is initialized, the 52 cards in a standard deck are created and then shuffled using Durstenfeld's permutation algorithm, a common randomization technique. The now-shuffled deck is then read into a queue that the Deck class uses to implement its Deal method.
      A more sophisticated toolkit might provide classes for hands and piles, for reshuffling discarded cards, and for encapsulating the rules of the game. This simple example demonstrates how classes can be used to encapsulate game or business logic or any other kind of functionality into a nice clean package.

A Few Technical Points
      There are some pitfalls which you should be aware of when using VBScript classes, particularly in Active Server Pages (ASP). First, there is the infamous circular reference problem. VBScript knows that it can release the memory consumed by object X when there are no other objects with references to X. But consider this situation:


 Class MyClass
     Public Foo
 End Class
 Set X = New MyClass
 Set Y = New MyClass
 
 Set X.Foo = Y
 Set Y.Foo = X
 
 Set X = Nothing
 Set Y = Nothing
At the end of this little program, neither X nor Y have been released because each still has another object referencing it. Yet the programmer has no way of getting to that memory because all the variable references have been thrown away. This is a circular reference memory leak.
      Unlike Microsoft® JScript®, VBScript does not have a circular reference detector. Fortunately, that memory will eventually get released. When the hosting application shuts down the script engine, the VBScript engine breaks all the circular references and destroys all unterminated objects. Still, you should avoid circular references because they do waste resources.
      A somewhat more technical problem can arise when using VBScript classes on the ASP Web server. The ASP server is multithreaded and assigns a different thread to each page request (and hence each script engine). But VBScript class instances are apartment-threaded objects, which means that they must run on the thread that created them. Therefore, attempts to use one instance of a VBScript class on two different pages via storage in Session or Application scope are doubly doomed to failure. First of all, since apartment-threaded objects must run on the same thread, but different pages are on different threads, you'll take a major performance hit if you use one object on two pages with Session scope. Any calls on the second thread have to be marshaled back to the first thread, which is both a source of resource contention and simply a slow operation. Putting apartment-threaded objects into Session scope is not recommended, and putting them into Application scope is illegal. However, that point is somewhat moot because, as I mentioned earlier, when the script engine shuts down, all of the objects that it owns are terminated.
      Remember, the methods of a class are script and therefore need a script engine to run them. When the page associated with the class is served to the client, the script engine that created the class is closed and all the class instances go with it.
      Fortunately, it's not all bad news: Windows Scripting Components allow you to encapsulate script functionality into objects as well, and WSC objects have lifetimes independent of the page that created them. Unfortunately, they do still suffer from the threading limitations. Space does not permit a full explanation of WSCs here, so watch for further articles on the Windows Script Component technology.

Conclusion
      VBScript classes are an extremely useful and powerful addition to the programming language constructs available in VBScript. The improved encapsulation afforded by classes allows programmers to define their own abstract data types and to hide implementation details and other secrets from the consumers of the objects. Furthermore, classes make it easy to verify that the internal data of the class is consistent. This means that larger programs are more easily understood and therefore easier to debug and maintain—just watch out for those circular references!


From the November 1999 issue of Microsoft Internet Developer.