Creating a Class
A class describes the things in your application, such as customers or products. Each piece of data associated with the class is defined as a property of the class. Each set of functionality associated with the class is defined as a method of the class.
For example, the Purchase Tracker sample application works with products. The products are described by a Product class. Each attribute of the products, such as name, number, description, price, and so on, is represented in the class as a property. Each process that must be performed for the products, such as retrieving, saving, and so on, is defined in the class as a method.
A single item, such as an individual product (a ring or sword, for example), is represented by an object created from the class. Because an object is an instance of a class, the act of creating an object from the class is called instantiation.
A common metaphor is to think of the class as the blueprint, and the object as the building constructed from the blueprint. Any number of buildings can be created from the same blueprint. Another metaphor is a cookie cutter. The class is the cookie cutter, and the objects are all the cookies created from the cookie cutter.
If you are building a nontrivial application, build it as a set of layers, as described in Chapter 2, "Designing Software." Implement each layer as a separate project in a solution, as described in Chapter 3, "Building Projects." This gives you separately compiled components, one for each layer.
You build each layer as a set of classes. The user interface layer is comprised of a set of form classes (as shown in Chapter 4, "Building the User Interface Layer") following the user interface design. The business logic layer (as described in this chapter) includes the set of classes you build following the implementation design. The data layer (detailed in Chapter 8, "Building the Data Access Layer") contains classes that provide the interaction between the database and the business logic layer.
When you construct the business logic layer, it is important to define the pertinent set of classes. For each class, you define the appropriate properties and methods. This ensures that the correct set of information and logic is encapsulated in each class, making it easier to work with and maintain the class.
You normally define one class for each key thing involved with the application. For example, the Purchase Tracker sample application has products, customers, and purchases. The products map to a Product class, with properties to manage product information and methods to retrieve, save, and perform any other required processing on product information. The customers map to a Customer class, and so on. For more information on defining classes for your application, see Chapter 2.
You can also define classes for other implementation logic. For example, you could create a class to manage application logging or security. These implementation-based classes were also discussed in Chapter 2.
This section details the process of creating a class. You can use these techniques to create each class needed by your application.
Adding a Class to a Project
There are many ways to add a class to a project. As discussed in the preceding chapter, adding a form project item actually adds two class files to the project. It adds a class in one file with a .vb extension and a partial class in another file with a .designer.vb extension.
When building the business logic layer, you normally add one class project item for each business object class (such as Product and Customer) and one for each implementation class (such as Logging and Security).
To add a class to a project:
Right-click the project in Solution Explorer and select Add | New Item from the context menu, or select Project | Add New Item from the main menu bar.
Alternatively, you could select Add | Class from the context menu, or select Project | Add Class from the main menu bar.
Select the Class template, name the class, and click the Add button.
If you created your own class template using the steps in Chapter 3, you can use your template here.
Use standard naming conventions for your class name. The most common standard is to name the class using the singular name of the business entity or implementation feature represented by the class. For products the class name would be Product, for logging the class name would be Logging, and so on.
Visual Studio creates the class file with a .vb extension, adds it to Solution Explorer, and then displays the class in the Code Editor.
When Visual Studio creates the class file, it automatically generates the class declaration as follows:
Public Class Product End Class
You can add any number of classes to your projects as needed by your application. Regardless of the class's purpose or location, the basic process of building a class is the same.
Documenting the Class
It is always a good idea to add documentation for a class immediately after adding the class. By adding the documentation right away, you focus on the class's purpose, which helps you keep the class encapsulated. It is also much easier to document each class as you go along instead of facing the large task of going back later and documenting all the classes.
To document the class:
- Open the class in the Code Editor.
- Move the insertion point immediately before the word Public in the Public Class statement.
Type three comment markers, defined in Visual Basic as apostrophes ('''), and press the Enter key.
The XML comments feature automatically creates the structure of your class documentation as follows:
''' <summary> ''' ''' </summary> ''' <remarks></remarks> Public Class Product End Class
Type a summary of the class's purpose between the summary tags and any remarks between the remark tags.
Your documentation may be similar to this:
''' <summary> ''' Provides product management features such as ''' retrieving product data and saving product changes ''' </summary> ''' <remarks>Use this class to work with products ''' </remarks>
Use the summary tags to describe the class and the remarks tags to add supplemental information. The summary is the most important tag because it is the one used by Visual Studio.
When you provide a summary of the class using XML comments, your class displays documentation about itself in appropriate places within Visual Studio, such as in the List Members box, shown in Figure 5.1. Open the List Members box by typing a part of the class name in the Code Editor and pressing Ctrl+Spacebar or by selecting Edit | Intellisense | List Members from the main menu bar or by clicking the Display an Object Member List icon on the Text Editor toolbar.
Figure 5.1 The documentation provided in the List Members box is the summary defined in the XML documentation for the class.
Using XML comments to document your classes makes it easier for you and other developers to work with your classes.
Organizing the Code Structure
Code that is organized is much easier to maintain, because you can quickly find the code that needs to be changed. When you standardize this organizational structure, any member of the team can quickly locate and modify any class code, because the code structure of each class is the same.
A class is normally composed of a set of properties and public and private methods. Public methods are methods that can be called from outside of the class, and private methods are those used only within the class.
You define the organizational structure of a class using regions, as described in Chapter 3. If you built a class template using the information in Chapter 3 and then used that template when creating your class, these regions are already defined in your code. If not, you can create the regions as follows:
Public Class Product #Region " Properties" #End Region #Region " Public Methods" #End Region #Region " Private Methods" #End Region End Class
Add any other regions as needed to define the standard code structure for your classes. For example, if your class has a constructor, you can add a Constructor region as defined later in this section.
As you develop the code for the application, properties are added to the Properties region, public methods are added to the Public Methods region, and so on. To further aid in the organization, you can insert the properties and methods within each region in alphabetical order.
You can also put regions within regions. So, you can put each method in its own region within the Private Methods or Public Methods region. This makes it easier to focus on the code, because you can close all methods except for the one you are working with.
Instantiating an Object
Once a class is defined, you can create objects from the class. This is called object instantiation. You then use the object to access the properties and methods defined in the class.
To create an object from a class:
Declare an object variable.
For example:
Dim prod as Product
Create a new instance of the class, and assign the object variable to reference that new instance.
For example:
prod = New Product
Access the properties and methods for the object by using the object variable and a period (.).
For example:
prod.ProductName
Alternatively, you can accomplish the first two steps in one code line as follows:
Dim prod as New Product
This line declares the object variable and assigns it to reference a new object from the Product class.
Defining the Constructor
A constructor is a built-in method in a class that the .NET runtime executes when an object is first instantiated (created). You add code in the constructor to perform any initialization operations for a new object.
You define a constructor by creating a New method in the class as follows:
#Region " Constructors" Public Sub New() End Sub #End Region
The constructor executes when you create an object from the class. For example:
Dim prod as New Product
When the .NET runtime executes this line of code, it calls the New method in the Product class and runs any code in your constructor.
You can pass data into the constructor by defining parameters. The constructor is then called a parameterized constructor. For example, this constructor defines a productID as a parameter:
#Region " Constructors" Public Sub New(ByVal productID as Integer) End Sub #End Region
You pass the parameter to the constructor when creating the instance of the object:
Dim prod as New Product(1)
or
Dim prod as Product prod = New Product(1)
Multiple constructors can be defined for a class. For example, you could define a constructor with no parameters and one with a parameter. The .NET runtime knows which constructor to call based on the parameters passed to the constructor. Defining one method (in this case, New) with two different signatures is called overloading and is described in detail later in this chapter.
In many cases, you don't need any specialized code to be executed when the object is instantiated, so you don't need to create a constructor. If you don't create one, the runtime executes an empty constructor for you.
In other cases, you may want a more formal object creation pattern. The most common formal pattern for object creation is called the Factory pattern. A pattern is a reusable solution for a recurring problem. The Factory pattern defines a standard solution for creating objects. Instead of creating an instance of a class using the New keyword, the Factory pattern defines a method that creates and returns an instance of the class. This makes the process of creating object instances more explicit. See the "Additional Reading" section at the end of Chapter 1, "Introduction to OO in .NET," for more information on patterns.
If you elect to apply the Factory method, you no longer use this style of code to create an object:
Dim prod as Product prod = New Product
You instead use code like this:
Dim prod as Product prod = Product.Create()
The Create method, and the syntax used to call it, are defined in detail later in this chapter.
Use constructors or a Factory pattern method to define any code that must be executed when first creating an object from the class. Don't bother creating a constructor if you have no initialization code.
Defining the Destructor
A destructor is a built-in method in a class that the .NET runtime executes when an object is destroyed. You define a destructor by creating a Finalize method in the class. But using a destructor is not recommended, because it does not necessarily execute when you expect it to, and it may not execute at all.
It may seem that the destructor should execute when you specify that you no longer need the object. For example, the following code defines that you no longer need the specified object reference:
prod = Nothing
But this code does not destroy the object. It just releases the object, making it available for destruction.
Your code does not define when an object is destroyed—the .NET garbage collector does. The garbage collector is a memory manager that manages the allocation and release of memory for all .NET applications. The garbage collector performs garbage collection when it needs to. It then releases the memory allocated to a managed object and destroys the object if that object is no longer used. But because you cannot predict when the garbage collector will perform garbage collection, you don't know exactly when your object will be destroyed. Therefore, you cannot know when your destructor will be executed. (See the "Additional Reading" section for more information on the garbage collector.)
In most cases, you don't need to write any cleanup code that executes when an object is destroyed, so you don't need to care about this. You can just allow the garbage collector to destroy your object when it gets around to it.
But if your object works with unmanaged resources, you do need to write some cleanup code. Unmanaged resources are system resources that are not directly managed by the .NET runtime, such as database connections, window handles, open files, network connections, and graphic resources. You need to explicitly release unmanaged resources when your object is released.
Because the execution of the Finalize method is unpredictable, do not put the code to release unmanaged resources in the destructor. Create a Dispose method instead, and explicitly call Dispose when you release the object. Add code in the Dispose method to perform any cleanup activities required for your object—primarily, releasing any unmanaged resources used by your object. And since you are explicitly calling Dispose, you control when the unmanaged resources are released.
Define a Dispose method by implementing the .NET Framework IDisposable interface. Using the IDisposable interface to define your Dispose method ensures that you have a standardized programmatic interface for disposing of your objects.
To implement a Dispose method, do the following:
- Open the class in the Code Editor.
- Add the Implements statement to the class definition to implement the IDisposable interface:
Public Class Product Implements IDisposable
Press the Enter key after the name of the interface.
The Code Editor automatically adds the signatures for all the properties and methods defined in the interface and related code to the class. This generated code uses the recommended design pattern for proper cleanup of any unmanaged resources that your application uses.
Visual Studio adds two sets of generated code as part of the IDisposable interface implementation, defining two Dispose methods. The first part of the generated code provides a Dispose method that you can customize to your requirements:
Private disposedValue As Boolean = False 'To detect redundant calls ' IDisposable Protected Overridable Sub Dispose(ByVal disposing As Boolean) If Not Me.disposedValue Then If disposing Then ' TODO: free unmanaged resources when explicitly called End If ' TODO: free shared unmanaged resources End If Me.disposedValue = True End Sub
The disposedValue variable keeps track of whether the object has already been disposed so that it won't dispose it again.
The parameter passed to this customizable Dispose method defines whether Dispose is called from the IDisposable interface Dispose method (shown next). If so, the code should free any unmanaged resources, so put your cleanup code here, by the first TODO Task List comment. If you have shared resources (those defined without a specific instance), put that cleanup code by the second TODO Task List comment.
When all the unmanaged resources are freed, this code sets the disposedValue property so that it won't dispose again.
The second part of the generated code defines the implemented interface:
#Region " IDisposable Support" 'This code added by Visual Basic to correctly implement the 'disposable pattern. Public Sub Dispose() Implements IDisposable.Dispose 'Do not change this code. Put cleanup code in 'Dispose(ByVal disposing As Boolean) above. Dispose(True) GC.SuppressFinalize(Me) End Sub #End Region
This code is meant to be left unchanged. This interface Dispose method first calls the customizable Dispose method to perform the cleanup. It then calls the SuppressFinalize method on the garbage collector. SuppressFinalize tells the garbage collector that it does not need to perform further cleanup on the object because the object was already cleaned up by the customized Dispose method.
Unlike the Finalize destructor, the Dispose method is not called automatically. The code that created the object must manually call Dispose explicitly when destroying the object as follows:
prod.Dispose() prod = Nothing
If your code creates an object using the Using statement, as defined in the preceding chapter, the .NET runtime automatically calls the Dispose method at the end of the Using block, so you don't need to explicitly call Dispose. The Using statement also ensures that Dispose is called even if an error occurs within the Using block.
In this example, the Using statement is as follows:
Using prod As Product = New Product 'Code to work with the object here End Using
Or, if you are using a Create Factory pattern method:
Using prod As Product = Product.Create() 'Code to work with the object here End Using
If your class does not have or use any unmanaged resources, it can leave it up to the garbage collector to clean things up, and no Dispose method is needed. If your class does use unmanaged resources, implement a Dispose method using the IDisposable interface, as described in this section. Any code that creates an object from your class must then correctly destroy it when it is finished with it by calling the object's Dispose method or by using the Using statement.
Using Partial Classes
By convention, each business object class resides in a single class file. The Product class is in the Product.vb file, the Customer class is in the Customer.vb file, and so on. But that is not a requirement. A class can be divided between any number of class files.
You can break a class into two or more files by defining partial classes. Partial classes are used primarily in situations where you have a code generator that generates part of the class and custom code for the remainder of the class. By placing the generated code in a file separate from the custom code, you can more easily regenerate the generated code without affecting the custom code. Every time you add a form to a project, Visual Studio creates a partial class and generates the code defining the controls on your user interface in that class, as described in Chapter 4.
To define a partial class for your class:
Right-click the project in Solution Explorer and select Add | New Item from the context menu, or select Project | Add New Item from the main menu bar.
Alternatively, you could select Add | Class from the context menu, or select Project | Add Class from the main menu bar.
Select the Class template, name the class, and click the Add button.
You cannot have two code files with the same filename, so name the partial class with a unique name.
Visual Studio creates the class file with a .vb extension, adds it to Solution Explorer, and then displays the class in the Code Editor.
- In the Code Editor, add the Partial keyword to the class definition, and modify the class name to match the original class name.
For example, the class definition of a partial class for the Product class is as follows:
Partial Public Class Product End Class
When the application is built, the code in the file for the class and the files for any partial classes are combined into one logical class. So the runtime behaves as if there is only one class.
One other benefit of partial classes is the ability to separately define Option Strict. Your primary class can have Option Strict set to On, and your partial class can have Option Strict set to Off. This is useful if you have code that needs to work with objects without concern for conversion of their types, such as when calling components written in VB6 or other Component Object Model (COM)-based technologies.
Don't use partial classes unnecessarily. Dividing a class into multiple class files for no particular purpose makes it more difficult to maintain the class. It is more difficult to find where code resides and see how it interacts with other code in the class.
Use partial classes for the defined purpose—separating generated code from custom code. If you are not writing your own code generators, you may never need to create a partial class. If you use third-party code generators, you may notice the partial classes that they create.
Adding Multiple Classes to a Class File
A single class file can contain any number of classes. Although you normally define a class within its own class file, you can add support classes for that class directly in the same class file.
For example, say you define a ProductOutOfStockException class that is used only by the Product class. You can define this exception class in the same code file as the Product class:
End Class ' End of the Product Class <Serializable()> _ Public Class ProductOutOfStockException Inherits ApplicationException Public Sub New(ByVal message As String) MyBase.New(message) End Sub Public Sub New(ByVal message As String, _ ByVal inner As Exception) MyBase.New(message, inner) End Sub Public Sub New( _ ByVal info As _ System.Runtime.Serialization.SerializationInfo, _ ByVal context As _ System.Runtime.Serialization.StreamingContext) MyBase.New(info, context) End Sub End Class
This class inherits from ApplicationException to ensure that it behaves as an exception. It contains three methods, each of which calls the associated base class method. Using the same named methods with different parameters is described later, in the section "Overloading Methods." You can create your own exceptions any time using this style of exception class.
Don't put multiple business object classes in a single class file. Reserve this feature for adding support classes or exception classes only. For example, business object-unique exceptions are an excellent type of class to add to a business object class file.