Conceptually, OOP really isn't that complex. Because both humans and programmers interact with real-world objects and instances every day, it's pretty easy to wrap their minds around the idea of programming with objects. But how easy is it to communicate these object concepts to the computer through the Visual Basic compiler and the .NET Framework? Can it be done without weekly sessions on a shrink's comfy sofa? Duh! It's Visual Basic; of course it's easy. One reason objects are so easy in .NET is that they have to be. Everything in your .NET program is part of an object, and if everything about .NET was hard, you'd be reading a book on Macintosh development right about now. But it's not too hard because the Visual Basic implementation of objects parallels the conceptual ideas of objects. ClassesVisual Basic uses classes to define objects. The Class keyword starts the definition. Class Superhero ' ----- Class-related code goes here. End Class That's most of it: the Class keyword, and a name for the class ("Superhero" in this case). All classes reside in a namespace (discussed way back in Chapter 1). By default, your classes appear in a namespace that is named after your project. You can alter this in the project properties (to set the top-level namespace for your assembly) and with the Namespace statement (to indicate relative namespaces from your assembly's top-level namespace). Namespace GoodGuys Class Superhero End Class End Namespace You can add any number of classes to a namespace. Classes that use the same name, but that appear in different namespaces, are unrelated. The members of a class appear between the Class and End Class clauses. You can also split a class's definition into multiple source code files. If you do split up a class like this, at least one of the parts must include the keyword Partial in the definition. Partial Class Superhero As with variable definitions, classes are defined using one of the access modifier keywords: Public, Private, Protected, Friend, or Protected Friend. Flip back to Chapter 6, "Data and Data Types," in the "Variables" section, if you need a refresher course. The .NET Framework Class Libraries are simply loaded with classes and objects, and they are all pretty much defined with this simple keyword: Class. Class MembersCalling your class Superhero won't endow it with any special powers if you don't add any members to the class. All class members must appear between the Class and End Class boundaries, although if you use the Partial feature to break up your class, you can sprinkle the members among the different class parts in any way you wish. There are 11 different kinds of members you can include in your Visual Basic classes. Other books or documents may give you a different number, but they're wrong, at least if they organize things the way I do here.
Adding a nice variety of members to a class is a lot of fun. You can add class members in any variety, in any order, and in any quantity. If you add a lot of members, you might even get a quantity discount on Visual Studio from Microsoft, but I wouldn't hold your breath. Shared Class MembersNormally, objects (class instances) are greedy and selfish; they want to keep everything to themselves and not share with others. That's why each instance of a class you create has its own version of the data elements defined as class members. Even class methods and properties give the appearance of being distinct for each class instance. It's as if each object was saying, "I've got mine; get your own." It's this attitude that has led to what is now commonly called "class warfare." In an attempt to promote affability among software components and push for "kindlier and gentler" classes, Microsoft included the Shared keyword in its class design. The Shared keyword can be applied to Variable field, Sub method, Function method, and property members of your class. When defined, a shared member can be used without the need to create an instance of that class. You reference these shared members using just the class name and the member name. Class ClassWithSharedValue Public Shared TheSharedValue As Integer End Class ...later, in some other code... ClassWithSharedValue.TheSharedValue = 10 Shared members are literally "shared" by all instances of your class, and if public, by code outside of the class as well. Because they don't require an object instance, they are also limited to just those resources that don't require an object instance. This means that a shared method cannot access a non-shared variable field of the same class. Any class members that are not marked Shared are known as instance members. Overloaded Members and Optional ArgumentsOverloading of a method occurs by attaching the Overloads keyword to each of the overloaded members. Class House Public Overloads Sub PaintHouse() ' ----- Use the same color(s) as before. End Sub Public Overloads Sub PaintHouse(ByVal baseColor As Color) ' ----- Paint the house a solid color. End Sub Public Overloads Sub PaintHouse(ByVal baseColor As Color, _ ByVal trimColor As Color) ' ----- Paint using a main and a trim color. End Sub Public Overloads Sub PaintHouse(ByVal baseColor As Color, _ ByVal coats As Integer) ' ----- Possibly paint with many coats, of paint ' that is, not of fabric. End Sub End Class When you call the PaintHouse method, you must pass arguments that match one of the overloaded versions. Visual Basic determines which version to use based on the argument signature. If you pass the wrong type or number of arguments, the program will refuse to compile. Two of the overloaded members in this class look alike, except for the second coats argument. Public Overloads Sub PaintHouse(ByVal whichColor As Color) Public Overloads Sub PaintHouse(ByVal baseColor As Color, _ ByVal coats As Integer) Instead of defining two distinct methods, I could have combined them into a single method, and defined an optional argument for the coats parameter. Public Overloads Sub PaintHouse(ByVal baseColor As Color, _ Optional ByVal coats As Integer = 1) The Optional keyword can be used on any number of parameters, but no non-optional parameters can appear after them; the optional arguments must always be last in the list. Although the calling code might not pass a value for coats, .NET still requires that every parameter receive an argument. Therefore, each optional argument includes a default value using a simple assignment within the parameter definition. The optional argument coats uses a default value of 1 through the "= 1" clause. InheritanceVisual Basic supports inheritance, the joining of two classes in an ancestor-descendant relationship. To implement inheritance, define the base class, and then add the derived class using the keyword Inherits. What a surprise! Class Animal ' ----- Animal class members go here. End Class Class Mammal Inherits Animal ' ----- All members of Animal are automatically ' part of Mammal. Add additional Mammal ' features here. End Class The Inherits statement must appear at the start of the class definition, before the definition for any class members. It must include the name of exactly one other class, the base class. If you split up your derived class using the Partial keyword, you only need to use the Inherits statement in one of the parts. And because a derived class can only use a single base class, you're pretty much limited to using the Inherits statement only once per class. (The base class can be used in several different derived classes, and the derived class can further be used as a base class for other derived classes.) Derived classes automatically receive all defined members of the base class. If a derived class needs to provide special functionality for a member defined in the base class, it overrides the member in the base class. This is a two-step process: (1) the base class must allow its member to be overridden with the Overridable keyword; and (2) the base class must supply the overriding code using the Overrides keyword. Class Animal Public Overridable Sub Speak() MsgBox("Grrrr.") End Sub End Class Class Canine Inherits Animal Public Overrides Sub Speak() MsgBox("Bark.") End Sub End Class Any class that derives from Animal can now supply its own custom code for the Speak method. But the same is true for classes derived from Canine; the Overridable keyword is passed down to each generation. If you need to stop this attribute at a specific generation, use the NotOverridable keyword. This keyword is valid only when used in a derived class, because base class members are non-overridable by default. Class Canine Inherits Animal Public NotOverridable Overrides Sub Speak() MsgBox("Bark.") End Sub End Class There are times when it is not possible to write a truly generic method in the base class, and you want to require that every derived class define its own version of the method. Using the MustOverride keyword in the member definition enables this requirement. Class Animal Public MustOverride Sub DefenseTactic() End Class Members marked as MustOverride include no implementation code of their own because it would go unused. (Also notice that DefenseTactic has no closing "End Sub" statement.) Because there is no code associated with this member, the entire Animal class has a deficiency. If you created an instance of Animal and called its DefenseTactic method, panic would ensue within the application. Therefore, it is not possible to create instances of classes that contain MustOverride members. To note this limitation, the class is also decorated with the MustInherit keyword. MustInherit Class Animal Public MustOverride Sub DefenseTactic() End Class It won't be possible to create an instance of Animal directly, although you can derive classes from it, and create instances of those classes. Also, you can create an Animal variable (a reference type) and assign an instance of an Animal-derived class to it. Dim zooMember As Animal Dim monkey As New Simian ' Simian is derived from Animal zooMember = monkey Such code doesn't really seem fair to the base class. I mean, it defined all the core requirements for derived classes, but it doesn't get any of the credit because it can't be directly instantiated. But there is a way for a base class to control its own destiny, to take all of the glory for itself. It does this with the NotInheritable keyword. NotInheritable Class Animal End Class The only way to use a NotInheritable class is to create an instance of it; you cannot use it as the base class of another derived class. Inherits, MustInherit, NotInheritable, Overrides, Overridable, NotOverridablethis certainly isn't your grandmother's Visual Basic anymore. And there's still one more of these inimitable keywords: Shadows. When you override a base class member, the new code must use a definition that is identical to the one provided in the base class. That is, if you override a function method with two String arguments and an Integer return code, the overriding code must use that same signature. Shadowed members have no such requirements. A shadowed member matches an item in the base class "in name only;" everything else is up for grabs. You can even change the member type. If you have a Sub method named PeanutButter in a base class, you can shadow it in the derived class with a variable field (or constant, or enumeration, or nested class) also named PeanutButter. Class Food Public Sub PeanutButter() End Sub End Class Class Snack Inherits Food Public Shadows PeanutButter As String ' Hey, it's not even a "Sub" End Class Without the Shadows keyword in the Snack class, a compile-time error would occur. Creating Instances of ClassesStep one: designing classes. Step two: deriving classes. Step three: creating class instances. Step four: cha-cha-cha. Visual Basic uses the New keyword to create instances of your custom classes. Dim myPet As Animal = New Animal ' ----- Or... Dim myPet As New Animal ' ----- Or... Dim myPet As Animal myPet = New Animal The instance can then be used like any other .NET instance variable. Member access occurs using "dot" notation. myPet.Name = "Fido" You can also (within reason) pass instance variables between their base and derived variations. Dim myPet As Animal Dim myDog As Canine myDog = New Canine myDog.Name = "Fido" myPet = myDog ' Since Canine derives from Animal MsgBox(myPet.Name) ' Displays "Fido" If you have Option Strict set to On, there will be limits on your ability to convert between types, especially narrowing conversions (where the source data type will not always "fit" in the target variable). In such cases, you must use the CType function (or one of a few similar .NET and Visual Basic supplied functions) to enable the conversion. myDog = CType(myPet, Canine) Referring to class instances is simply a matter of referring to the variable or object that contains the instance. That is true for code that uses an instance from outside of the class itself. For the code within your class (such as in one of its methods), you refer to members of your class as if they were local variables (with no qualification), or use the special Me keyword. Class Animal Public Name As String Public Sub DisplayName() ' ----- Either of these lines will work. MsgBox(Name) MsgBox(Me.Name) End Sub End Class A similar keyword, MyClass, usually acts like the Me keyword, but it has some different functionality when a class instance is stored in a variable from a different (base or derived) class type. For instance, if you create an instance of Canine, but store it in an Animal variable, references using Me will focus on the Canine code, whereas references to MyClass will focus on the Animal code. I won't be using MyClass in the Library Project, and for most simple uses of class instances, you will never use it either. But there are times when it is important to differentiate between base and derived code, and this is the way to do it. The MyBase keyword references elements of the base class from which the current class derives. It references only the closest base class; if you have a class named Class5 that derives from Class4, which in turn derives from Class3, which derives from Class2, which derives from Class1, which eventually derives from System.Object, references to MyBase in the code of Class5 refer to Class4. Well, that's almost true. If you try to use MyBase.MemberName and MemberName doesn't exist in Class4, MyBase will search back through the stack of classes until it finds the first definition of MemberName. Class Animal Public Overridable Sub ObtainLicense() ' ----- Perform Animal-specific licensing code. End Sub End Class Class Canine Inherits Animal Public Overrides Sub ObtainLicense() ' ----- Perform Canine-specific licensing code, then... MyBase.ObtainLicense() ' Calls code from Animal class End Sub End Class Constructors and DestructorsClass instances have a lifetime: a beginning, a time of activity, and finally, thankfully, an end. The beginning of an object's lifetime occurs through a constructor; its final moments are dictated by a destructor before passing into the infinity of the .NET garbage collection process. Each class includes at least one constructor, whether explicit or implicit. If you don't supply one, .NET will at least perform minimal constructor-level activities, such as reserving memory space for each of the instance variable fields of your class. If you want a class to have any other startup-time logic, you must supply it through an explicit constructor. Constructors in Visual Basic are Sub methods with the name New. A New constructor with no arguments acts as the default constructor, called by default whenever a new instance of a class is needed. Class Animal Public Name As String Public Sub New() ' ----- Every animal must have some name. Name = "John Doe of the Jungle" End Sub End Class Without this constructor, new instances of Animal wouldn't have any name assigned to the Name field. And because String variables are reference types, Name would have an initial value of Nothing. Not very user friendly. A default constructor gives you a chance to provide at least the minimum needed data and logic for a new instance. You can provide additional custom constructors by adding more New methods, each with a different argument signature. Class Animal Public Name As String Public Sub New() ' ----- Every animal must have some name. Name = "John Doe of the Jungle" End Sub Public Sub New(ByVal startingName As String) ' ----- Use the caller-supplied name. Name = startingName End Sub Public Sub New(ByVal startingCode As Integer) ' ----- Build a name from a numeric code. Name = "Animal Number " & CStr(startingCode) End Sub End Class The following code demonstrates each constructor. MsgBox((New Animal).Name) ' Displays "John Doe of the Jungle" MsgBox((New Animal("Fido")).Name) ' Displays "Fido" MsgBox((New Animal(5)).Name) ' Displays "Animal Number 5" You can force the consumer of your class to use a custom constructor by excluding a default constructor from the class definition. Also, if you're deriving your class from anything other than System.Object, it's usually a good idea to call the base class's constructor as the first line of your derived constructor, although the default constructor in the base class will be called by default. Class Canine Inherits Animal Public Sub New() MyBase.New() ' Calls Animal.New() ' ----- Now add other code. End Sub End Class Killing a class instance is not as easy as it might seem. When you create local class instances in your methods, they are automatically destroyed when that method exits if you haven't assigned the instance to a variable outside of the method. If you create an instance in a method, and assign it to a class member, it will live on in the class member for the lifetime of the class, even though the method that created it has exited. But let's think only about local instances for now. An instance is destroyed when the routine exits. You can also destroy an instance immediately by setting its variable to Nothing. myDog = Nothing Setting the variable to a new instance will destroy any previous instance stored in that variable. myDog = New Canine myDog.Name = "Fido" myDog = New Canine ' Sorry Fido, you're gone When an object is destroyed, .NET calls a special method named Finalize, if present, to perform any final cleanup before removing the instance from memory. Finalize is implemented as a Protected method of the base System.Object class; you must override this method in your class to use it. Class Animal Protected Overrides Sub Finalize() ' ----- Cleanup code goes here. Be sure to call the ' base class's Finalize method. MyBase.Finalize() End Sub End Class So what's with that crack about killing instances being so hard? The problem is that .NET controls the calling of the Finalize method; it's part of the garbage collection process. The Framework doesn't continually clean up its garbage. It's like the service at your house; it gets picked up by the garbage truck once in a while. Until then, it just sits there, rotting, decaying, decomposing, and not having its Finalize method called. For most objects, this isn't much of a problem; who cares if the memory for a string gets released now or 30 seconds from now? But there are times when it is important to release acquired resources as quickly as possible. For instance, if you acquire a lock on an external hardware resource and only release it in the destructor, you could be holding that lock long after the application has exited. Talk about a slow death. There are two ways around this problem. One way is to add a separate cleanup method to your class that you expect any code using your class to call. This will workuntil some code forgets to call the method. (You should therefore also call this routine from the destructor.) The second method is similar, but it uses a Framework-supplied interface called IDisposable. (I'll talk about interfaces in a minute, so don't get too worried about all the code shown here.) Class Animal Implements IDisposable Protected Overrides Sub Finalize() ' ----- Cleanup code goes here. Be sure to call the ' base class's Finalize method. MyBase.Finalize() End Sub Public Overloads Sub Dispose() _ Implements IDisposable.Dispose ' ----- Put cleanup code here. Also make these calls. MyBase.Dispose() ' Only if base class is disposable. System.GC.SuppressFinalize(Me) End Sub End Class The SuppressFinalize method tells the garbage collector, "Don't call Finalize; I've already cleaned up everything." Any code that uses your class will need to call its Dispose method to perform the immediate cleanup of resources. So it's not too different from the first method, but it does standardize things a bit. Also, it enables the use of the Visual Basic Using statement. This block statement provides a structured method of cleaning up resources. Using myPet As New Animal ' ----- Code here uses the myPet. End Using ' ----- At this point, myPet is destroyed, and Dispose is ' called automatically by the End Using statement. InterfacesThe MustOverride and MustInherit keywords force derived classes to implement specific members of the base class. But what if you want the derived class to implement all members of the base class? You could use MustOverride next to each method and property, but a better way is to use an interface. Interfaces define abstract classes, classes consisting only of definitions, no implementation. (OOP purists will point out that a class with even just one MustOverride flag is also an abstract class. Fine.) The Interface statement begins the interface definition process. By convention, all interface names begin with the capital letter I. Interface IBuilding Function FloorArea() As Double Sub AlterExterior() End Interface As you see here, the syntax is a somewhat simplified version of the class definition syntax. All interface members are automatically public, so access modifiers aren't included. Only the definition line of each member is needed because there is no implementation. In addition to function and sub methods, interface members also include properties, events, other interfaces, classes, and structures. Interfaces can also derive from other interfaces (using the Inherits keyword), and automatically include all of the members of the base interface. Classes make use of interfaces as they wish. It's not like inheritance, where all base members become part of the derived class. A class can pick and choose which interface members to implement. Often you'll implement all interface members, but as a class designer, you have the power. You attach interfaces to your class using the Implements keyword. This same keyword is used later to indicate which class member defines which interface member. Class House Implements IBuilding Public Function FloorArea() As Double _ Implements IBuilding.FloorArea ' ----- Add implementation here. End Function Public Sub PaintHouse() Implements IBuilding.AlterExterior ' ----- Add implementation here. End Sub End Class Class implementations of interface members are not required to maintain the original interface member name (although the argument signature must match the one in the interface). In the sample code, the FloorArea kept the name of the equivalent interface member, but the AlterExterior member was implemented using the PaintHouse method. This makes possible some interesting code. Dim someHouse As New House Dim someBuilding As IBuilding someBuilding = someHouse someBuilding.AlterExterior() ' Calls someHouse.PaintHouse() Classes can only inherit from a single base class, but there is no limit on the number of interfaces that a class can implement. Class House Implements IBuilding, IDisposable Also, a single class member can implement multiple interface members. Public Sub PaintHouse() Implements _ IBuilding.AlterExterior, IContractor.DoWork So, why use interfaces? Interfaces provide a generic way to access common functionality, even among objects that have nothing in common. Classes named Animal, House, and Superhero probably have nothing in common in terms of logic, but they may all need a consistent way to clean up their resources. If they each implement the IDisposable interface, they gain that ability without the need to derive from some common base class. Modules and StructuresIn addition to classes, Visual Basic provides two related object definition features: structures and modules. Although they have different names than "class," they still act a lot like classes, but with different features enabled or disabled. Modules provide a place to include general global code and data values in your application or assembly. All members of a module are Shared. In fact, a module acts just like a class with the Shared keyword added to each member, yet with one major difference: There is no inheritance relationship allowed with modules. You cannot create a derived module from a base class or module, nor can you use a module as a base for any other type. Modules are a carryover from pre-.NET versions of Visual Basic, which included "Modules" for all non-Form code. Friend Module GenericDataAndCode ' ----- Application-global constant. Public Const AllDigits As String = "0123456789" ' ----- Application-global function. Public Function GetEmbeddedDigits( _ ByVal sourceString As String) As String End Sub End Module You cannot create an instance of a module. As with classes, modules appear in the context of a namespace. Unlike a class with shared members, you do not need to specify the module name to use the module member. All module members act as global variables and methods, and can be used immediately in any other code in your application without further qualification. Structures are much more like classes than are modules. Classes implement reference types, but structures implement value types. All structures derive from System.ValueType (which in turn derives from System.Object). As such, they act like the core Visual Basic data types, such as Integer. You can create instances of a structure using the same syntax used to create class instances. However, you cannot use a structure as the base for another derived structure. And while you can include a constructor in your structure, destructors are not supported. Because of the way that structures are stored and used in a .NET application, they are well suited to simple data types. You can include any number of members in your structure, but it is best to keep things simple. |