Chapter 25. OOP Concepts

This chapter explains the fundamental ideas behind object-oriented programming (OOP). It describes the three main features of OOP languages: encapsulation, inheritance, and polymorphism. It explains the benefits of these features and describes how you can take advantage of them in Visual Basic.

This chapter also describes method overloading. In a sense, overloading provides another form of polymorphism. It lets you create more than one definition of the same class method, and Visual Basic decides which version to use based on the parameters the program passes to the method.

Many of the techniques described in this chapter help you define a new class, but extension methods let you modify an existing class. For example, you could use extension methods to add new features to the String class, perhaps to make it encrypt and decrypt text.

Many of the ideas described in this chapter will be familiar to you from your experiences with forms, controls, and other building blocks of the Visual Basic language. Those building blocks are object-oriented constructs in their own rights, so they provide you with the benefits of encapsulation, inheritance, and polymorphism whether you knew about them or not.

CLASSES

A class is a programming entity that gathers all the data and behavior that characterizes some sort of programming abstraction. It wraps the abstraction in a nice, neat package with well-defined interfaces to outside code. Those interfaces determine exactly how code outside of the class can interact with the class. A class determines which data values are visible outside of the class and which are hidden. It determines the routines that the class supports and their availability (visible or hidden).

A class defines properties, methods, and events that let the program work with the class:

  • A property is some sort of data value. It may be a simple value (such as a name or number), or it may be a more complex item (such as an array, collection, or object containing its own properties, methods, and events).

  • A method is a subroutine or function. It is a piece of code that makes the object defined by the class do something.

  • An event is an action notification defined by the class. An event calls some other piece of code to tell it that some condition in a class object has occurred.

For a concrete example, imagine a Job class that represents a piece of work to be done by an employee. This class might have the properties shown in the following table.

PROPERTY

PURPOSE

JobDescription

A string describing the job

EstimatedHours

The number of hours initially estimated for the job

ActualHours

The actual number of hours spent on the job

Status

The job's status (New, Assigned, In Progress, or Done)

ActionTaken

A string describing the work performed, parts installed, and so forth

JobCustomer

An object of the Customer class that describes the customer (name, address, phone number, service contract number, and so on)

AssignedEmployee

An object of the Employee class that describes the employee assigned to the job (name, employee ID, Social Security number, and so on)

The JobDescription, EstimatedHours, ActualHours, Status, and ActionTaken properties are relatively simple string and numeric values. The JobCustomer and AssignedEmployee properties are objects themselves with their own properties, methods, and events.

This class might provide the methods shown in the following table.

METHOD

PURPOSE

AssignJob

Assign the job to an employee

BillJob

Print an invoice for the customer after the job is finished

EstimatedCost

Returns an estimated cost based on the customer's service contract type and EstimatedHours

The class could provide the events shown in the following table to keep the main program informed about the job's progress.

EVENT

PURPOSE

Created

Occurs when the job is first created

Assigned

Occurs when the job is assigned to an employee

Rejected

Occurs if an employee refuses to do the job, perhaps because the employee doesn't have the right skills or equipment to do the work

Canceled

Occurs if the customer cancels the job before it is worked

Finished

Occurs when the job is finished

In a nutshell, a class is an entity that encapsulates the data and behavior of some programming abstraction such as a Job, Employee, Customer, LegalAction, TestResult, Report, or just about anything else you could reasonably want to manipulate as a single entity.

After you have defined a class, you can create instances of the class. An instance of the class is an object of the class type. For example, if you define a Job class, you can then make an instance of the class that represents the specific job of installing a new computer for a particular customer. The process of creating an instance is called instantiation.

There are a couple of common analogies to describe instantiation. One compares the class to a blueprint. After you define the class, you can use it to create any number of instances of the class, much as you can use the blueprint to make any number of similar houses (instances).

Another analogy compares a class definition to a cookie cutter. When you define the cookie cutter, you can use it to make any number of cookies (instances).

Note that Visual Basic is jam-packed with classes. Every type of control and component (Form, TextBox, Label, Timer, ErrorProvider, and so forth) is a class. The parent classes Control and Component are classes. Even Object, from which all other classes derive, is a class. Whenever you work with any of these (getting or setting properties, calling methods, and responding to events), you are working with instances of classes.

Because all of these ultimately derive from the Object class, they are often simply called objects. If you don't know or don't care about an item's class, you can simply refer to it as an object.

The following sections describe some of the features that OOP languages in general, and Visual Basic in particular, add to this bare-bones definition of a class.

ENCAPSULATION

A class's public interface is the set of properties, methods, and events that are visible to code outside of the class. The class may also have private properties, methods, and events that it uses to do its job. For example, the Job class described in the previous section provides an AssignJob method. That method might call a private FindQualifiedEmployee function, which looks through an employee database to find someone who has the skills and equipment necessary to do the job. That routine is not used outside of the class, so it can be declared private.

The class may also include properties and events hidden from code outside of the class. These hidden properties, methods, and events are not part of the class's public interface.

The class encapsulates the programming abstraction that it represents (a Job in this ongoing example). Its public interface determines what is visible to the application outside of the class. It hides the ugly details of the class's implementation from the rest of the world. Because the class hides its internals in this way, encapsulation is also sometimes called information hiding.

By hiding its internals from the outside world, a class prevents exterior code from messing around with those internals. It reduces the dependencies between different parts of the application, allowing only those dependencies that are explicitly permitted by its public interface.

Removing dependencies between different pieces of code makes the code easier to modify and maintain. If you must change the way the Job class assigns a job to an employee, you can modify the AssignJob method appropriately. The code that calls the AssignJob routine doesn't need to know that the details have changed. It simply continues to call the method and leaves the details up to the Job class.

Removing dependencies also helps break the application into smaller, more manageable pieces. A developer who calls the AssignJob method can concentrate on the job at hand, rather than on how the routine works. This makes developers more productive and less likely to make mistakes while modifying the encapsulated code.

The simpler and cleaner a class's public interface is, the easier it is to use. You should try to hide as much information and behavior inside a class as possible while still allowing the rest of the program to do its job. Keep properties, methods, and events as simple and focused as possible. When you write code that the class needs to use to perform its tasks, do not expose that code to the outside program unless it is really necessary. Adding extra features complicates the class's public interface and makes the programmer's job more difficult.

This can be a troublesome concept for beginning programmers. Exposing more features for developers to use gives them more power, so you might think it would make their jobs easier. Actually, it makes development more difficult. Rather than thinking in terms of giving the developer more power, you should think about giving the developer more things to worry about and more ways to make mistakes. Ideally, you should not expose any more features than the developer will actually need.

Note that you can achieve many of the benefits of encapsulation without classes. Decades before the invention of object-oriented languages, programmers were making code libraries that encapsulated functionality. For example, a library of trigonometry functions would expose public function calls to calculate sines, cosines, tangents, arctangents, and so forth. To perform its calculations, the library might contain private lookup tables and helper functions that calculate series expansions. The tables and helper functions were hidden from the main program calling the library functions.

One big benefit of classes over this sort of library encapsulation is intuitiveness. When you make a class named Job, Customer, or Employee, anyone familiar with your business can make a lot of assumptions about what the class represents. Even if you don't know how the Job class works, you can probably guess what a new ReassignJob method would do. As long as everyone has a common vision of what the class does, you get an extra level of intuitive encapsulation that you don't necessarily get with a more procedural approach.

INHERITANCE

Inheritance is the process of deriving a child class from a parent class. The child class inherits all of the properties, methods, and events of the parent class. It can then modify, add to, or subtract from the parent class. Making a child class inherit from a parent class is also called deriving the child class from the parent, and subclassing the parent class to form the child class.

For example, you might define a Person class that includes variables named FirstName, LastName, Street, City, State, Zip, Phone, and Email. It might also include a DialPhone method that dials the person's phone number on the phone attached to the computer's modem.

You could then derive the Employee class from the Person class. The Employee class inherits the FirstName, LastName, Street, City, State, Zip, Phone, and Email variables. It then adds new EmployeeId, SocialSecurityNumber, OfficeNumber, Extension, and Salary variables. This class might override the Person class's DialPhone method, so it dials the employee's office extension instead of the home phone number.

You can continue deriving classes from these classes to make as many types of objects as you need. For example, you could derive the Manager class from the Employee class and add fields such as Secretary, which is another Employee object that represents the manager's secretary. Similarly, you could derive a Secretary class that includes a reference to a Manager object. You could derive ProjectManager, DepartmentManager, and DivisionManager from the Manager class, Customer from the Person class, and so on for other types of people that the application needed to use. Figure 25-1 shows these inheritance relationships.

You can derive classes from other classes to form quite complex inheritance relationships.

Figure 25.1. You can derive classes from other classes to form quite complex inheritance relationships.

Inheritance Hierarchies

One of the key benefits of inheritance is code reuse. When you derive a class from a parent class, the child class inherits the parent's properties, methods, and events, so the child class gets to reuse the parent's code. That means you don't need to implement separate FirstName and LastName properties for the Person, Employee, Manager, Secretary, and other classes shown in Figure 25-1. These properties are defined only in the Person class, and all of the other classes inherit them.

Code reuse not only saves you the trouble of writing more code but also makes maintenance of the code easier. Suppose that you build the hierarchy shown in Figure 25-1 and then decide that everyone needs a new BirthDate property. Instead of adding a new property to every class, you can simply add it to the Person class and all of the other classes inherit it.

Similarly, if you need to modify or delete a property or method, you only need to make the change in the class where it is defined, not in all of the classes that inherit it. If the Person class defines a SendEmail method and you must modify it so that it uses a particular e-mail protocol, you only need to change the routine in the Person class, not in all the classes that inherit it.

Refinement and Abstraction

You can think about the relationship between a parent class and its child classes in two different ways. First, using a top-down view of inheritance, you can think of the child classes as refining the parent class. They provide extra detail that differentiates among different types of the parent class.

For example, suppose that you start with a broadly defined class such as Person. The Person class would need general fields such as name, address, and phone number. It would also need more specific fields that do not apply to all types of person. For example, employees would need employee ID, Social Security number, office number, and department fields. Customers would need customer ID, company name, and discount code fields. You could dump all these fields in the Person class, but that would mean stretching the class to make it play two very different roles. A Person acting as an Employee would not use the Customer fields, and vice versa.

A better solution is to derive new Employee and Customer classes that refine the Person class and differentiate between the types of Person.

A bottom-up view of inheritance considers the parent class as abstracting common features out of the child classes into the parent class. Common elements in the child classes are removed and placed in the parent class. Because the parent class is more general than the child classes (it includes a larger group of objects), abstraction is sometimes called generalization.

Suppose that you are building a drawing application and you define classes to represent various drawing objects such as Circle, Ellipse, Polygon, Rectangle, and DrawingGroup (a group of objects that should be drawn together). After you work with the code for a while, you may discover that these classes share a lot of functionality. Some, such as Ellipse, Circle, and Rectangle, are defined by bounding rectangles. All the classes need methods for drawing the object with different pens and brushes on the screen or on a printer.

You could abstract these classes and create a new parent class named Drawable. That class might provide basic functionality such as a simple variable to hold a bounding rectangle. This class would also define a DrawObject routine for drawing the object on the screen or printer. It would declare that routine with the MustOverride keyword, so each child class would need to provide its own DrawObject implementation, but the Drawable class would define its parameters.

Sometimes you can pull variables and methods from the child classes into the parent class. In this example, the Drawable class might include Pen and Brush variables that the objects would use to draw themselves. Putting code in the parent class reduces the amount of redundant code in the child classes, making debugging and maintenance easier.

To make the classes more consistent, you could even change their names to reflect their shared ancestry. You might change their names to DrawableEllipse, DrawablePolygon, and so forth. This not only makes it easier to remember that they are all related to the Drawable class but also helps avoid confusion with class names such as Rectangle that are already used by Visual Basic.

The Drawable parent class also allows the program to handle the drawing objects more uniformly. It can define a collection named AllObjects that contains references to all the current drawing's objects. It could then loop through the collection, treating the objects as Drawables, and calling their DrawObject methods. The section "Polymorphism" later in this chapter provides more details.

Usually application architects define class hierarchies using refinement. They start with broad general classes and then refine them as necessary to differentiate among the kinds of objects that the application will need to use. These classes tend to be relatively intuitive, so you can easily imagine their relationships.

Abstraction often arises during development. As you build the application's classes, you notice that some have common features. You abstract the classes and pull the common features into a parent class to reduce redundant code and make the application more maintainable.

Refinement and abstraction are useful techniques for building inheritance hierarchies, but they have their dangers. Designers should be careful not to get carried away with unnecessary refinement or overrefinement. For example, suppose that you define a Vehicle class. You then refine this class by creating Auto, Truck, and Boat classes. You refine the Auto class into Wagon and Sedan classes and further refine those for different drive types (four-wheel drive, automatic, and so forth). If you really go crazy, you could define classes for specific manufacturers, body styles, and color.

The problem with this hierarchy is that it captures more detail than the application needs. If the program is a repair dispatch application, it might need to know whether a vehicle is a car or truck. It will not need to differentiate between wagons and sedans, different manufacturers, or colors. Vehicles with different colors have the same behaviors as far as this application is concerned. Creating many unnecessary classes makes the object model harder to understand and can lead to confusion and mistakes. (I even worked on one project that failed due to an overly complicated object model.)

Avoid unnecessary refinement by only refining a class when doing so lets you capture new information that the application actually needs to know.

Just as you can take refinement to ridiculous extremes, you can also overdo class abstraction. Because abstraction is driven by code rather than intuition, it sometimes leads to unintuitive inheritance hierarchies. For example, suppose that your application needs to mail work orders to remote employees and invoices to customers. If the WorkOrder and Invoice classes have enough code in common, you might decide to give them a common parent class named MailableItem that contains the code needed to mail a document to someone.

This type of unintuitive relationship can confuse developers. Because Visual Basic doesn't allow multiple inheritance, it can also cause problems if the classes are already members of other inheritance hierarchies. You can avoid some of those problems by moving the common code into a library and having the classes call the library code. In this example, the WorkOrder and Invoice classes would call a common set of routines for mailing documents and would not be derived from a common parent class.

Unnecessary refinement and overabstracted classes lead to overinflated inheritance hierarchies. Sometimes the hierarchy grows very tall and thin. Other times, it may include several root classes (with no parents) on top of only one or two small classes each. Either of these can be symptoms of poor designs that include more classes than necessary. If your inheritance hierarchy starts to take on one of these forms, you should spend some time reevaluating the classes. Ensure that each adds something meaningful to the application and that the relationships are reasonably intuitive. Too many classes with confusing relationships can drag a project to a halt as developers spend more time trying to understand the hierarchy than they spend implementing the individual classes.

If you are unsure whether to add a new class, leave it out. It's usually easier to add a new class later if you discover that it is necessary than it is to remove a class after developers start using it.

"Has-a" and "Is-a" Relationships

Refinement and abstraction are two useful techniques for generating inheritance hierarchies. The "has-a" and "is-a" relationships can help you understand whether it makes sense to make a new class using refinement or abstraction.

The "is-a" relationship means one object is a specific type of another class. For example, an Employee "is-a" specific type of Person object. The "is-a" relation maps naturally into inheritance hierarchies. Because an Employee "is-a" Person, it makes sense to derive the Employee class from the Person class.

The "has-a" relationship means that one object has some item as an attribute. For example, a Person object "has-a" street address, city, state, and ZIP code. The "has-a" relation maps most naturally to embedded objects. For example, you could give the Person class Street, City, State, and Zip properties.

Suppose that the program also works with WorkOrder, Invoice, and other classes that also have street, city, state, and ZIP code information. Using abstraction, you might make a HasPostalAddress class that contains those values. Then you could derive Person, WorkOrder, and Invoice as child classes. Unfortunately, that makes a rather unintuitive inheritance hierarchy. Deriving the Person, WorkOrder, and Invoice classes from HasPostalAddress makes those classes seem closely related when they are actually related almost coincidentally.

A better solution would be to encapsulate the postal address data in its own PostalAddress class and then include an instance of that class in the Person, WorkOrder, and Invoice classes. The following code shows how the Person class would include an instance of the PostalAddress class:

Public Class Person
    Public MailingAddress As PostalAddress
    ...
End Class

You make a parent class through abstraction in part to avoid duplication of code. The parent class contains a single copy of the common variables and code, so the child classes don't need to have their own separate versions for you to debug and maintain. Placing an instance of the PostalAddress class in each of the other classes provides the same benefit without confusing the inheritance hierarchy.

You can often view a particular relationship as either an "is-a" or "has-a" relationship. A Person "has-a" postal address. At the same time, a Person "is-a" thing that has a postal address. Use your intuition to decide which view makes more sense. One hint is that postal address is easy to describe whereas thing that has a postal address is more awkward and ill-defined. Also, think about how the relationship might affect other classes. Do you really want Person, WorkOrder, and Invoice to be siblings in the inheritance hierarchy? Or would it make more sense for them to just share an embedded class?

Adding and Modifying Class Features

Adding new properties, methods, and events to a child class is easy. You simply declare them as you would in any other class. The parent class knows nothing about them, so the new items are added only to the child class.

The following code shows how you could implement the Person and Employee classes in Visual Basic. The Person class includes variables that define the FirstName, LastName, Street, City, State, Zip, Phone, and Email values. It also defines the DialPhone method. The version shown here simply displays the Person object's Phone value. A real application could connect to the computer's modem and dial the number.

The Employee class inherits from the Person class. It declares its own EmployeeId, SocialSecurityNumber, OfficeNumber, Extension, and Salary variables. It also defines a new version of the DialPhone method that displays the Employee object's Extension value rather than its Phone value. The DialPhone method in the Person class is declared with the Overridable keyword to allow derived classes to override it. The version defined in the Employee class is declared with the Overrides keyword to indicate that it should replace the version defined by the parent class.

Public Class Person
    Public FirstName As String
    Public LastName As String
    Public Street As String
    Public City As String
    Public State As String
    Public Zip As String
Public Phone As String
    Public Email As String

    ' Dial the phone using Phone property.
    Public Overridable Sub DialPhone()
        MessageBox.Show("Dial " & Me.Phone)
    End Sub
End Class

Public Class Employee
    Inherits Person
    Public EmployeeId As Integer
    Public SocialSecurityNumber As String
    Public OfficeNumber As String
    Public Extension As String
    Public Salary As Single

    ' Dial the phone using Extension property.
    Public Overrides Sub DialPhone()
        MessageBox.Show("Dial " & Me.Extension)
    End Sub
End Class

A class can also shadow a feature defined in a parent class. When you declare a property, method, or event with the Shadows keyword, it hides any item in the parent that has the same name. This is very similar to overriding, except that the parent class does not have to declare the item as overridable and the child item needs only to match the parent item's name.

For example, the parent might define a SendMail subroutine that takes no parameters. If the child class defines a SendMail method that takes some parameters and uses the Shadows keyword, the child's version hides the parent's version.

In fact, the child and parent items don't even need to be the same kind of item. For example, the child class could make a subroutine named FirstName that shadows the parent class's FirstName variable. This type of change can be confusing, however, so usually you should only shadow items with similar items.

The following code shows how the Employee class might shadow the Person class's SendMail subroutine. The Person class displays the mailing address where it would send a letter. A real application might print a letter on a specific printer for someone to mail. The Employee class shadows this routine with one of its own, which displays the employee's office number instead of a mailing address.

Public Class Person
    ...
    ' Send some mail to the person's address.
    Public Sub SendMail()
        MessageBox.Show("Mail " & Street & ", " & City & ", " &
            State & " " & Zip)
    End Sub
End Class
Public Class Employee
    Inherits Person
    ...
    ' Send some mail to the person's office.
    Public Shadows Sub SendMail()
        MessageBox.Show("Mail " & OfficeNumber)
    End Sub
End Class

Interface Inheritance

When you derive one class from another, the child class inherits the properties, methods, and events defined by the parent class. It inherits both the definition of those items as well as the code that implements them.

Visual Basic also enables you to define an interface. An interface defines a class's behaviors, but does not provide an implementation. After you have defined an interface, a class can use the Implements keyword to indicate that it provides the behaviors specified by the interface. It's then up to you to provide the code that implements the interface.

For example, consider again the MotorHome class. Visual Basic does not allow a class to inherit from more than one parent class, but a class can implement as many interfaces as you like. You could define an IVehicle interface (by convention, interface names begin with the capital letter I) that defines properties of vehicles (number of wheels, horsepower, maximum speed, acceleration, and so forth) and an IHouse interface that defines properties of living spaces (square feet, number of bedrooms, number of bathrooms, and so forth). Now, you can make the MotorHome class implement both of those interfaces. The interfaces do not provide any code, but they do declare that the MotorHome class implements the interface's features.

Like true inheritance, interface inheritance provides polymorphism (see the next section, "Polymorphism," for more details on this topic). You can use a variable having the type of the interface to refer to objects that define the interface. For example, suppose that the Employee class implements the IPerson interface. Then you can use a variable of type IPerson to refer to an object of type Employee.

Suppose that the people collection contains Employee objects. The following code uses a variable of type IPerson to display the objects' names:

For Each person As IPerson In people
    Debug.WriteLine(person.FirstName & " " & person.LastName)
Next person

POLYMORPHISM

Roughly speaking, polymorphism means treating one object as another. In OOP terms, it means that you can treat an object of one class as if it were from a parent class.

For example, suppose that Employee and Customer are both derived from the Person class. Then you can treat Employee and Customer objects as if they were Person objects because in a sense they are Person objects. They are specific types of Person objects. After all, they provide all of the properties, methods, and events of a Person object.

Visual Basic enables you to assign a value from a child class to a variable of the parent class. In this example, you can place an Employee or Customer object in a Person variable, as shown in the following code:

Dim emp As New Employee    ' Create an Employee.
Dim cust As New Customer   ' Create a Customer.
Dim per As Person          ' Declare a Person variable.
per = emp                  ' Okay. An Employee is a Person.
per = cust                 ' Okay. A Customer is a Person.
emp = per                  ' Not okay. A Person is not necessarily an Employee.

One common reason to use polymorphism is to treat a collection of objects in a uniform way that makes sense in the parent class. For example, suppose that the Person class defines the FirstName and LastName fields. The program could define a collection named AllPeople and add references to Customer and Employee objects to represent all the people that the program needs to work with. The code could then iterate through the collection, treating each object as a Person, as shown in the following code:

For Each per As Person In AllPeople
    Debug.WriteLine(per.FirstName & " " & per.LastName)
Next Per

You can only access the features defined for the type of variable you actually use to refer to an object. For example, if you use a Person variable to refer to an Employee object, you can only use the features defined by the Person class, not those added by the Employee class.

If you know that a particular object is of a specific subclass, you can convert the variable into a more specific variable type. The following code loops through the AllPeople collection and uses the TypeOf statement to test each object's type. If the object is an Employee, the code uses DirectCast to convert the object into an Employee object. It can then use the Employee object to perform Employee-specific tasks.

Similarly, the code determines whether the object is a Customer object. If it is, the code converts the generic Person variable into a Customer variable and uses the new variable to perform Customer-specific tasks, as shown here:

For Each per As Person In AllPeople
    If TypeOf per Is Employee Then
        Dim emp As Employee = DirectCast(per, Employee)
        ' Do something Employee-specific.
        ...
    ElseIf TypeOf per Is Customer Then
        Dim cust As Customer = DirectCast(per, Customer)
        ' Do something Customer-specific.
        ...
    End If
Next per

METHOD OVERLOADING

Visual Basic .NET enables you to give a class more than one method with the same name but with different parameters. The program decides which version of the method to use based on the parameters being passed to the method.

For example, the Person class shown in the following code has two constructors named New. The first takes no parameters and initializes the object's FirstName and LastName variables to default values. The second overloaded constructor takes two strings as parameters and uses them to initialize FirstName and LastName.

Public Class Person
    Public FirstName As String
    Public LastName As String

    Public Sub New()
        FirstName = "<first>"
        LastName = "<last>"
    End Sub

    Public Sub New(ByVal first_name As String, ByVal last_name As String)
        FirstName = first_name
        LastName = last_name
    End Sub
End Class

The following code uses these constructors. The first statement passes no parameters to the constructor, so Visual Basic uses the first version of the New method. The second statement passes two strings to the constructor, so Visual Basic uses the second constructor.

Dim person1 As New Person()
Dim person2 As New Person("Rod", "Stephens")

A common technique for providing constructors that take different numbers of arguments is to make the simpler constructors call those with more parameters. In the following code, the empty constructor calls a constructor that takes two parameters, passing it default values:

Public Class Person
    Public FirstName As String
    Public LastName As String

    Public Sub New()
        Me.New("<first>", "<last>")
    End Sub

    Public Sub New(ByVal first_name As String, ByVal last_name As String)
        FirstName = first_name
        LastName = last_name
    End Sub
End Class

Two overloaded methods cannot differ only by optional parameters. For example, the first_name and last_name parameters in the previous constructor could not both be optional. If they were, Visual Basic .NET could not tell which version of the New subroutine to call if the program passed it no parameters. Although you cannot make the parameters optional in the second constructor, you can get a similar result by combining the two constructors, as shown in the following code:

Public Class Person
    Public FirstName As String
    Public LastName As String

    Public Sub New(
     Optional ByVal first_name As String = "<first>",
     Optional ByVal last_name As String = "<last>")
        FirstName = first_name
        LastName = last_name
    End Sub
End Class

Overloaded functions also cannot differ only in their return types. For example, you cannot have two versions of a function with the same name and parameters but different return types.

If you have Option Strict set to Off, there are many circumstances where Visual Basic performs automatic type conversion. In that case, it might not be able to decide which of two functions to use if they differ only in return type. For example, suppose that one version of the TotalCost function returns an Integer and another version returns a Double. If you set a string variable equal to the result of the function, Visual Basic wouldn't know whether to use the Integer version or the Double version.

EXTENSION METHODS

Extension methods let you add new subroutines or functions to an existing class without rewriting it or deriving a new class from it even if you don't have access to the class's source code.

To make an extension method, place the System.Runtime.CompilerServices.Extension attribute before the method's declaration. Then make a normal subroutine or function that takes one or more parameters. The first parameter determines the class that the method extends. The method can use that parameter to learn about the item for which them method was called. The other parameters are passed into the method so it can use them to perform its chores.

For example, the following code adds a MatchesRegexp subroutine to the String class. The Extension attribute tells Visual Basic that this is an extension method. The method's first parameter is a String so this method extends the String class. The second parameter is a regular expression. The method returns True if the String matches the regular expression.

' Return True if a String matches a regular expression.
<Extension()>
Public Function MatchesRegexp(ByVal the_string As String,
 ByVal regular_expression As String) As Boolean
    Dim reg_exp As New Regex(regular_expression)
    Return reg_exp.IsMatch(the_string)
End Function

The program can use the extension methods just as if they were part of the String class. The following code uses the MatchesRegexp method to decide whether the phone_number variable contains a value that looks like a valid United States phone number:

If Not phone_number.MatchesRegexp("^[2-9]d{2}-d{4}$") Then
    MessageBox.Show("Not a valid phone number")
End If

If used haphazardly, extension methods can blur the purpose of a class. They can make the class do things for which it was never intended. They add behaviors that the class's original authors did not have in mind. The result may be a class that does too many poorly defined or unrelated things, and that is confusing and hard to use properly. They weaken the class's encapsulation by adding new features that are not hidden within the control's code.

If you have access to the class's source code, make changes to the class within that code. Then if there is a problem, at least all of the code is together within the class. If you really need to add new methods to a class that is outside of your control, such as adding new methods to String and other classes defined by Visual Basic and the .NET Framework, you can use extension methods.

SUMMARY

Classes are programming abstractions that group data and related behavior in nicely encapsulated packages. After you define a class, you can instantiate it to create an instance of the class. You can interact with the new object by using its properties, methods, and events.

Inheritance enables you to derive one class from another. You can then add, remove, or modify the behavior that the child class inherits from the parent class. Sometimes it makes sense to think of the classes in inheritance hierarchies in a top-down manner, so child classes refine the features of their parents. At other times, it makes sense to use a bottom-up view and think of a parent class as abstracting the features of its children.

Interface inheritance lets you define some of the features of a class without using true class inheritance. This gives you another method for using polymorphism and lets you build classes that, in a sense, appear to inherit from multiple parents.

Polymorphism enables you to treat an object as if it were of an ancestor's type. For example, if the Manager class inherits from Employee and Employee inherits from Person, then you can treat a Manager object as if it were a Manager, Employee, or Person.

In addition to these features, Visual Basic .NET enables you to overload a class's subroutines, functions, and operators. It lets you create different methods with the same name but different parameters. The compiler selects the right version of the method based on the parameters you pass to it. Extension methods even let you add new subroutines and functions to existing classes when you don't have access to the class's source code.

These object-oriented concepts provide the general background you need to understand classes in Visual Basic. Chapter 26, "Classes and Structures," describes the specifics of classes and structures in Visual Basic .NET. It shows how to declare and instantiate classes and structures and explains the differences between the two.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.35.54