Inheritance and Polymorphism - Advanced Topics - Beginning Object-Oriented Programming with C# (2012)

Beginning Object-Oriented Programming with C# (2012)

Part V

Advanced Topics

Chapter 16: Inheritance and Polymorphism

Chapter 17: Printing and Threading

Chapter 18: Web Programming

Chapter 16

Inheritance and Polymorphism

What you will learn in this chapter:

· What inheritance is

· How inheritance simplifies program code

· What base classes and derived classes are

· What the protected access specifier is

· What polymorphism is

· How polymorphism can simplify your programs

· What extension methods are

wrox.com code downloads for this chapter

You can find the Wrox.com code downloads for this chapter at www.wrox.com/remtitle.cgi?isbn=9781118336922 on the Download Code tab. The code in the Chapter16 folder is individually named according to the names throughout the chapter.

This chapter expands the concept of inheritance. Although you have been using inheritance since the first chapter, not much has been said about it. The reason for the delay is because inheritance doesn't make much sense until you understand and appreciate OOP. My guess is that you do see what OOP brings to the table and are ready to dig into the topic of inheritance. In this chapter you learn the details of inheritance necessary to use it properly in your own programs.

As you read this chapter, keep in mind that inheritance was added to OOP languages to make the programmer's life easier. Although the end user may not see any advantages to inheritance and polymorphism, you can have a greater appreciation of how programmers can make program code simpler.

What Is Inheritance?

Until now, inheritance has been a concept sitting in the background, making your life easier every time you designed a user interface for your programs. Indeed, you have probably glossed over that the program line

public class frmMain : Form

that appears in every program you've written has enabled you to inherit all the basic functionality of a Windows form without needing to write any of that code. The colon in the preceding statement could be verbalized as “inherits from.”

The concept of inheritance is built upon the notion that many objects share similar properties. In the preceding statement, you state that you want to create a new class named frmMain that inherits all the functionality of a basic Windows form. Inheriting this functionality means that you don't need to write, test, debug, and maintain that inherited code. Therefore, the driving force behind inheritance is to simplify writing code. Inheritance makes it possible for your code to extend one class to suit your specific needs. Simply stated, inheritance is the ability to take one class and extend that class to suit a similar, albeit different, purpose. An example can help explain inheritance.

An Inheritance Example

Some time ago I was contracted to write a program for a real estate investor. The type of real estate the investor purchased could be classified as apartments, commercial properties (such as small strip malls), and residential homes. I sat down with the investor and asked her to describe the type of information that she needed to track with her investments. Table 16.1 is taken from my notes as she described her needs.

Table 16.1 Real Estate Types and Their Properties

Apartments

Commercial

Residential

Purchase price

Purchase date

Address

Purchase date

Address

Purchase price

Address

Rent per month

Purchase date

Monthly mortgage payment

Purchase price

Square feet

Insurance

Property taxes

Number of bedrooms

Property taxes

Insurance

Number of bathrooms

Covered parking

Mortgage payment

Basement?

Storage units

Parking spaces

Fireplace(s)

Number of bedrooms

Restroom facilities

Garage size (cars)

Number of bathrooms

Handicap parking

Rent per month

Rent per month

Lot size

Given the data requirements presented in Table 16.1, I could design the three classes with properties that would track the information she required. Looking at the information she needed, you can simplify each property type by removing those pieces of information common to all investment types. Table 16.2 shows these common properties.

Table 16.2 Properties Common to All Types of Real Estate

Information Common to All Properties

Purchase price

Purchase date

Address

Property taxes

Insurance

Mortgage payment

Rent per month

If you remove these common pieces of information from each investment type, you can simplify Table 16.1 to what is shown in Table 16.3. (This process to factor out duplicated data is similar to the normalization process discussed for databases.)

Table 16.3 Real Estate Types and Their Unique Properties

Apartments

Commercial

Residential

Covered parking

Parking spaces

Square feet

Storage units

Restroom facilities

Number of bedrooms

Number of bedrooms

Handicap parking

Number of bathrooms

Number of bathrooms

Basement?

Fireplace(s)

Garage size (cars)

Lot size

Less information is contained in Table 16.3 than in Table 16.1 because of the common information all three property types share in Table 16.2 is removed. You can express the relationships for the data as shown in the UML class diagrams in Figure 16.1. (You will learn about the removeSnow()method later in the section titled “The virtual Keyword.”)

Figure 16.1 UML class diagrams

image

The Base and Derived Classes

As you can see in Figure 16.1, I used the UML notation to give a first approximation of how to organize the data for the investment types. At the top of the figure is clsBuilding. clsBuilding, which contains all the properties common to all the investment types and is called the base class. You can think of the base class as a common denominator of data for all the classes. (This is not a new concept; you've been inheriting all of a Windows form's properties using the :Form expression in frmMain in all your programs.)

Any classes that want to use the properties and methods of the base class are called a derived class. For Figure 16.1, clsBuilding is the base class, and classes clsApartment, clsCommercial, and clsHome are derived classes. You also hear the base class referred to as the parent class and a derived class as a child class. The interpretations are the same.

Referring to Figure 16.1 you can notice that three arrows point from the derived classes to the base class. Each of these arrows can be verbalized as “is a.” That is, clsApartment “is a” clsBuilding. Using straight English, you can say, “An apartment object is a type of building object.” Likewise, a commercial object is a type of building object. The arrows in the UML diagram indicate the nature of the inherited relationships that exist between classes.

The protected Access Specifier

Referring to Figure 16.1, you can see the seven common properties all investment types share in clsBuilding with the data type for each property indicated. However, unlike the public (+) and private (-) access specifiers you learned in Chapter 9, a new access specifier (#) is used. The sharp sign (#) denotes the protected access specifier. The protected access specifier is needed because of the symbiotic relationship ( “is a”) formed between the base class and the derived classes.

To understand why the protected class is used, consider how things would work if you were limited to either a public or a private access specifier. First, consider the private access specifier. If you use the private access specifier in clsBuilding, each method and property in clsBuilding has class scope. As you already know, this means that those properties and methods would not be visible outside the class. Stated another way, any private property or method in clsBuilding would not be available to any outside class, including the three derived classes. Therefore, nothing in the program would have access to the base properties and methods. If you can't access them, they are virtually worthless to the derived classes.

Now consider the other alternative: making all the properties and methods public. If you did that, you just threw away all the benefits that encapsulation brings to the party. Not only can the derived classes gain access to the properties and methods of the base class, but so can every other object in the program. As you learned in earlier chapters, encapsulation enables you to protect your data from evil agents who want to wreak havoc on your program.

Using either the public or private access specifier poses a dilemma: two choices, both bad. The protected access specifier solves this problem. The protected access specifier enables all derived classes to have access to those properties and methods defined with the protected access specifier in the base class. By your using the sharp sign in Figure 16.1 before the properties defined in clsBuilding, all three derived classes have access to those properties. However, any object in the program that is not derived from the base class does not have direct access to those protected properties. That is, protected properties and methods appear as private properties and methods to all but the derived class(es). The protected access specifier similar to a high school clique in that the base and derived classes can share information that people outside the clique don't know about. This enables the base and derived classes to encapsulate the data that they need without exposing it outside those symbiotic classes.

Just to drive the point home, given a line in clsBuilding,

protected decimal purchasePrice;

you could have the line,

purchasePrice = 150000M;

in clsHome and it would be perfectly acceptable. The reason is that the protected keyword for the definition of purchasePrice in clsBuilding is completely in scope within clsHome. Therefore, protected data definitions in the base class are within scope for any of its derived classes.

Advantages of Inherited Relationships

You might be asking what inheritance gets you. Well, to begin with, if you didn't have the inherited relationships shown in Figure 16.1, each of the derived classes would need its own copy of the data shown in Table 16.2. That would also mean that each of those properties would need its own getand set property methods. The same would be true for any methods that might be shared in the derived classes, such as RemoveSnow(), shown in Figure 16.1.

Inheritance enables you to write less code by sharing properties and methods between the base and derived classes. Writing less (duplicate) code means less testing, debugging, and maintenance. It also follows that if you need to change a protected property or method, you must change it in only one place, and all the derived classes can immediately take advantage of it.

The next step is to put everything that we've discussed thus far into a program. The next Try It Out provides the code to implement our investment property problem.

Try It Out: Inheritance Example (Chapter16ProgramInheritance.zip)

Now write a simple version of the real estate investor program. Figure 16.2 shows a sample run of the program you write. Because this is a fairly large program, only parts of the code are shown here. You should use the download file for this project.

1. Create a new project in the usual manner.

2. Load the source files into the project from Chapter16ProgramInheritance.zip..

Figure 16.2 Sample program run

image

At the present time, all you want the program to do is display a description of each property type. You show the sample investment properties by clicking the Show Properties button.

How It Works

The program discussion began with the base clsBuilding class; Listing 16-1 shows the code.

Listing 16-1: Source Code for clsBuilding (clsBuilding.cs)

using System;

public class clsBuilding

{

//--------------------- Symbolic constants -----------------

public const int APARTMENT = 1;

public const int COMMERCIAL = 2;

public const int HOME = 3;

//--------------------- Instance variables -----------------

protected string address;

protected decimal purchasePrice;

protected decimal monthlyPayment;

protected decimal taxes;

protected decimal insurance;

protected DateTime datePurchased;

protected int buildingType;

string[] whichType = {"", "Apartment", "Commercial", "Home" };

//--------------------- Constructor ------------------------

public clsBuilding()

{

address = "Not closed yet";

}

public clsBuilding(string addr, decimal price, decimal payment,

decimal tax, decimal insur, DateTime date,

int type):this()

{

if (addr.Equals("") == false)

address = addr;

purchasePrice = price;

monthlyPayment = payment;

taxes = tax;

insurance = insur;

datePurchased = date;

buildingType = type;

}

//--------------------- Property Methods -------------------

public string Address

{

get

{

return address;

}

set

{

if (value.Length != 0)

address = value;

}

}

public decimal PurchasePrice

{

get

{

return purchasePrice;

}

set

{

if (value > 0M)

purchasePrice = value;

}

}

public decimal MonthlyPayment

{

get

{

return monthlyPayment;

}

set

{

if (value > 0M)

monthlyPayment = value;

}

}

public decimal Taxes

{

get

{

return taxes;

}

set

{

if (value > 0M)

taxes = value;

}

}

public decimal Insurance

{

get

{

return insurance;

}

set

{

if (value > 0M)

insurance = value;

}

}

public DateTime DatePurchased

{

get

{

return datePurchased;

}

set

{

if (value.Year > 2008)

datePurchased = value;

}

}

public int BuildingType

{

get

{

return buildingType;

}

set

{

if (value >= APARTMENT && value <= HOME)

buildingType = value;

}

}

//--------------------- General Methods --------------------

/*****

* Purpose: Provide a basic description of the property

*

* Parameter list:

* string[] desc a string array to hold description

*

* Return value:

* void

*

* CAUTION: Method assumes that there are 3 elements in array

******/

public void PropertySummary(string[] desc)

{

desc[0] = "Property type: " + whichType[buildingType] +

", " + address +

", Cost: " + purchasePrice.ToString("C") +

", Monthly payment: " + monthlyPayment.ToString("C");

desc[1] = " Insurance: " + insurance.ToString("C") +

" Taxes: " + taxes.ToString("C") +

" Date purchased: "+ datePurchased.ToShortDateString();

desc[2] = " ";

}

}

The code begins by defining several symbolic constants followed by the property members of clsBuilding. As specified in your design, each property method uses the protected access specifier. The code also provides for the property get and set methods for each of the properties so that the user can change the state of the object.

Symbolic Constants Versus Enumerated Types

Symbolic constants are used in many programs throughout this text. If you think about it, however, you could also use enumerated types in the same way. For example, you could replace the symbolic constants shown in Listing 16-1 with this:

public enum bldgType {APARTMENT = 1, COMMERCIAL, HOME}

Then you could use the enum in the BuildingType() like this:

set

{

if (value >= (int) bldgType.APARTMENT &&

value <= (int) bldgType.HOME)

{

buildingType = value;

}

}

The int cast is required in the if statement because value is defined as an int for the property. I find the syntax using the enum clumsy and prefers the use of symbolic constants. Select the style you prefer and use it consistently.

The method PropertySummary() is used to provide a summary of the building's description using the properties associated with each property. The method builds an array of strings, which are then returned to the caller in frmMain to display the property description in a listbox object. (Listing 16-2 presents the frmMain code.) The output generated by the PropertySummary() method looks like that shown in Figure 16.2.

There are two constructors in Listing 16-1. The first constructor assumes that you want to set the properties for the building by separate calls to the property methods:

public clsBuilding()

{

address = "Not closed yet";

}

The constructor also sets the default address to a message that suggests you haven't actually purchased the property yet. The second constructor assumes that you want to initialize the properties when the building object is instantiated:

public clsBuilding(string addr, decimal price, decimal payment,

decimal tax, decimal insur, DateTime date,

int type):this()

{

if (addr.Equals("") == false) {

address = addr;

}

purchasePrice = price;

monthlyPayment = payment;

taxes = tax;

insurance = insur;

datePurchased = date;

buildingType = type;

}

Good coding practice of calling the first constructor via the this() call to the first (no-argument) constructor is followed. This also means that if you pass in a property address in addr, the second constructor overwrites the “Not closed yet” string with the address.

Listing 16-2 shows the frmMain code that drives the application.

Listing 16-2: The frmMain Code for the Application

using System;

using System.Windows.Forms;

public class frmMain : Form

{

DateTime myTime; // instance members

clsBuilding myBldg;

clsApartment myApt;

clsCommercial myComm;

clsHome myHome;

private ListBox lstMessages;

private Button btnShow;

private Button btnRemoveSnow;

private Button btnClose;

#region Windows code

public frmMain()

{

InitializeComponent(); // Initialize the form

myTime = DateTime.Now;

myBldg = new clsBuilding(); // A base object

// Derived objects

myApt = new clsApartment("123 Jane Dr., Cincinnati, OH

45245", 550000, 6000, 15000, 3400, myTime, 1);

myComm = new clsCommercial("4442 Parker Place, York, SC

29745",1200000, 9000, 22000, 8000, myTime, 2);

myHome = new clsHome("657 Dallas St, Ringgold, GA 30736",

260000, 1100, 1750, 900, myTime, 3);

}

public static void Main()

{

frmMain main = new frmMain();

Application.Run(main);

}

// Show each of the properties…

private void btnShow_Click(object sender, EventArgs e)

{

string[] desc = new string[3];

myApt.PropertySummary(desc);

ShowProperty(desc);

myComm.PropertySummary(desc);

ShowProperty(desc);

myHome.PropertySummary(desc);

ShowProperty(desc);

}

private void ShowProperty(string[] str)

{

int i;

for (i = 0; i < str.Length; i++)

{

lstMessages.Items.Add(str[i]);

}

}

private void btnClose_Click(object sender, EventArgs e)

{

Close();

}

}

In a “real” program you would likely have textbox objects that would fill in each of the properties for the different types of buildings. This code initializes the buildings using the parameterized constructors. You can see the data in the frmMain() constructor method.

Notice how the descriptions display in the listbox object, For example,

myApt.PropertySummary(desc);

ShowProperty(desc);

calls the PropertySummary() method using the myApt object.

Listing 16-3 shows the code for the three derived classes. (The three class listings are separated with double-dashed lines to make it easier to see where one class ends and another begins.) If you look closely, you'll notice that clsApartment does not have a method namedPropertySummary(), yet Figure 16.2 shows the correct information for the apartment property. The same is true for clsCommercial and clsHome: Neither has a PropertySummary() method. How does the program display the correct information for each building? Obviously, all three buildings use the PropertySummary() method from the base class to display their properties.

Listing 16-3: Source for clsApartment (clsApartment.cs)

using System;

class clsApartment : clsBuilding

{

//--------------------- Instance variables -----------------

private int units;

private decimal rentPerUnit;

private double occupancyRate;

//--------------------- Constructor ------------------------

public clsApartment():base()

{

}

public clsApartment(string addr, decimal price, decimal payment,

decimal tax, decimal insur, DateTime date,

int type) :

base(addr, price, payment, tax,

insur, date, type)

{

buildingType = type; // Apartment type from base

}

//--------------------- Property Methods -------------------

public int Units

{

get

{

return units;

}

set

{

if (value > 0)

units = value;

}

}

public decimal RentPerUnit

{

get

{

return rentPerUnit;

}

set

{

if (value > 0M)

rentPerUnit = value;

}

}

public double OccupancyRate

{

get

{

return occupancyRate;

}

set

{

if (value > 0.0)

occupancyRate = value;

}

}

//--------------------- General Methods --------------------

public override string RemoveSnow()

{

return "Called John's Snow Removal: 859.444.7654";

}

}

================================================================

using System;

class clsCommercial : clsBuilding

{

//--------------------- Instance variables -----------------

private int squareFeet;

private int parkingSpaces;

private decimal rentPerSquareFoot;

//--------------------- Constructor ------------------------

public clsCommercial(string addr, decimal price, decimal payment,

decimal tax, decimal insur, DateTime date,

int type) :

base(addr, price, payment, tax,

insur, date, type)

{

buildingType = type; // Commercial type from base

}

//--------------------- Property Methods -------------------

public int SquareFeet

{

get

{

return squareFeet;

}

set

{

if (value > 0)

squareFeet = value;

}

}

public int ParkingSpaces

{

get

{

return parkingSpaces;

}

set

{

parkingSpaces = value;

}

}

public decimal RentPerSquareFoot

{

get

{

return rentPerSquareFoot;

}

set

{

if (value > 0M)

rentPerSquareFoot = value;

}

}

//--------------------- General Methods --------------------

public override string RemoveSnow()

{

return "Called Acme Snow Plowing: 803.234.5566";

}

}

===============================================================

using System;

class clsHome : clsBuilding

{

//--------------------- Instance variables -----------------

private int squareFeet;

private int bedrooms;

private double bathrooms;

private decimal rentPerMonth;

//--------------------- Constructor ------------------------

public clsHome(string addr, decimal price, decimal payment,

decimal tax, decimal insur, DateTime date,

int type) :

base(addr, price, payment, tax,

insur, date, type)

{

buildingType = 3; // Home type from base

}

//--------------------- Property Methods -------------------

public int SquareFeet

{

get

{

return squareFeet;

}

set

{

if (value > 0)

squareFeet = value;

}

}

public int BedRooms

{

get

{

return bedrooms;

}

set

{

bedrooms = value;

}

}

public double BathRooms

{

get

{

return bathrooms;

}

set

{

bathrooms = value;

}

}

public decimal RentPerMonth

{

get

{

return rentPerMonth;

}

set

{

if (value > 0M)

rentPerMonth = value;

}

}

//--------------------- General Methods --------------------

}

Each of the three building type begins with a line similar to

class clsApartment : clsBuilding

This is what establishes the “is a” relationship depicted in Figure 16.1. (This is no different than using the “: Form” that you've seen with virtually every form's class definition you've used throughout this text.) By tying each derived class to the base building class using this syntax, you can use the PropertySummary() found in clsBuilding in all the derived classes.

One more thing: Derived classes cannot inherit constructors. You must write a constructor for a derived class. If the base class doesn't have a default constructor, the constructor for the derived class must call the base constructor using the base keyword, as shown in clsApartment:

public clsApartment(string addr, decimal price, decimal payment,

decimal tax, decimal insur, DateTime date,

int type) :

base(addr, price, payment, tax,

insur, date, type)

The keyword base is used here to pass parameters used to construct the clsApartment object on to the clsBuilding constructor. (The concept is similar to the this construct used with multiple constructors.)

Base Classes Are Not Derived Classes

The capability for a derived class to use a method in the base class is a one-way relationship. That is, the derived class can call a base class method, but the base class cannot directly call a derived class method. For example, suppose clsCommercial contains a public method named HouseCleaning(). If myBldg is a clsBuilding object, you cannot use

myBldg.HouseCleaning(); // Error!!

The compiler throws an error and informs you that it cannot find a definition for HouseCleaning(). This conclusion is true even if you use the public access specifier for the HouseCleaning() method. The reason you can't perform a base-to-derived method call is that the “is a” relationship shown in Figure 16.1 is a one-way street. That is, a clsCommercial object assumes all the property and methods of a clsBuilding object, but a clsBuilding object does not assume all the properties and methods of a clsCommercial object.

Abstract Classes

As I mentioned earlier, the base class is typically used to serve as a repository for all the properties shared among the derived classes. However, it is possible that the base class is so nondescript that instantiating an object of the base class doesn't make sense. For example, you might create a tree class called clsDeciduous that has the properties leafColor, barkStyle, matureHeight, and ringCount. The derived classes might be clsOak, clsMaple, clsWillow and so on. The problem is that even though all the derived classes have leaves, bark, a mature height, and a ring count, there are so many trees with these characteristics that it makes no sense to instantiate a clsDeciduous object. Only the details found in the derived classes, with the base class, have enough information to make an object useful.

To prevent instantiation of the base class, use the abstract keyword:

public abstract class clsDecidious

{

// The class code…

}

If a class is defined by means of the abstract keyword, as shown here, you cannot instantiate an object of that class. This means that a statement like the following draws a compiler error telling you that you cannot instantiate an object of an abstract class:

clsDecidious myLeafyTree = new clsDecidious();

If you can't instantiate an object of the class, why use it?

By defining a class using the abstract keyword, you are telling the users of the class two things. First, they cannot instantiate an object of this class, which tips them off to the second reason. Second, and more important, the user must define derived classes to capture the functionality embodied in the base class. Indeed, there is no reason to use the base class in the absence of derived classes.

If you define a method using the abstract keyword, the derived classes must implement the method. There is no code in the base class for the method. In this way, abstract classes and methods are similar to interfaces in that interfaces contain no code either. However, interfaces cannot contain constructors.

Polymorphism

Chapter 1 mentions that polymorphism is one of the three pillars of object-oriented programming. At that time I dismissed the topic, saying that the word polymorphism is derived from the Greek meaning “many shapes” and that was all that was said. Now that you understand what inheritance is, you are ready to more completely appreciate what polymorphism is.

Instead of sticking with the concept of “many shapes,” perhaps the definition should be amended to mean “many messages.” In essence, polymorphism means that you can send the same message to a group of different classes and that each class will know how to respond correctly to that message.

Try It Out: Using Polymorphism

Consider the following example. Where I live, if more than 2 inches of snow falls, you are supposed to get out and shovel your walkways. Let's further assume that our software should notify someone at each property location when our property manager sees that 2 or more inches of snow has fallen. (I will just pretend that there is an electrical hookup between the software and the phone system. I'll use things displayed in the listbox object as an indicator that the call(s) have been made.)

What you want to do is add a button that the manager can click when he sees that more than 2 inches of snow is on the ground. A sample run of the program is shown in Figure 16.3.

Figure 16.3 Snow removal output

image

Notice that the apartment and commercial buildings are given a phone number to call for snow removal, but none is given for people who live in a rented home. The assumption is that, as part of their lease agreement, home renters are required to shovel the walks.

The code you downloaded already has the Remove Snow button and code in it. This exercise simply explains how polymorphism can be implemented.

How It Works

The first thing you need to do is modify the base class to process the snow-removal message. The code added to clsBuilding is as follows:

public virtual string RemoveSnow()

{

return whichType[buildingType] +

": No snow removal service available.";

}

The virtual Keyword

The snow removal method in clsBuilding, RemoveSnow(), is defined via the keyword virtual. The word virtual is used to identify that the method may be overridden in a derived class. Stated differently, a virtual method can be replaced by a different definition of the method in any class derived from this base class.

If you refer to Figure 16.1, you can see that you provide RemoveSnow() methods for both clsApartment and clsCommercial objects. The code for these two methods is as follows:

public override string RemoveSnow() // clsApartment

{

return "Apartment: Call John's Snow Removal: 859.444.7654";

}

public override string RemoveSnow() // clsCommerical

{

return "Commercial: Call Acme Snow Plowing: 803.234.5566";

}

Figure 16.1 also shows that there is no RemoveSnow() method for clsHome. People who rent homes were informed when they signed their lease that they were responsible for snow removal.

The override Keyword

As you might guess, the override keyword goes hand in hand with the virtual keyword. Whereas virtual tells the reader of the code that the method may be overridden, the override keyword tells her that this method is overriding a method found in the class from which it inherits.

Normally, the derived class overrides a virtual method defined in the base class. However, you can define a new class from a derived class. For example, you could add clsRecreational to clsHome, perhaps with new properties for lakeFrontage, boatRamp, availableFishingTackle, and so on. The definition of that class might be as follows:

public class clsRecreational : clsHome

{

// Class code

}

You can keep extending the class definitions as far as you think you need them. (If you want to prevent further inheritance from a class, you can use the sealed keyword as a modifier in the class definition, as in public sealed clsLastClass. Using the sealed keyword means that further derivations from this class are not permitted.)

Most of the time, however, you use override with a virtual method defined in the base class.

Sending the Snow-Removal Message

The new user interface for frmMain now contains a button that issues the snow-removal messages. It is the responsibility of each class to figure out what to do. The button click event code for the snow removal message is as follows:

private void btnRemoveSnow_Click(object sender, EventArgs e)

{

lstMessages.Items.Add(myApt.RemoveSnow());

lstMessages.Items.Add(myComm.RemoveSnow());

lstMessages.Items.Add(myHome.RemoveSnow());

lstMessages.Items.Add("");

}

Each derived class is sent the same message via the method call to RemoveSnow(). However, the message displayed is dictated by whatever process the derived class wants to attach to the method call.

Note that clsHome doesn't even have a RemoveSnow() method, yet it still displays the following message:

Home: No snow removal service available.

As you no doubt have already figured out, because clsHome has no RemoveSnow() method, the RemoveSnow() method in the base class is used. In other words, clsHome chose not to override the RemoveSnow() method but instead to use the “default” method as written in the base class.

The nice thing about using inheritance and polymorphic behavior is that each object knows the specifics of how it is supposed to react to whatever events or methods are called, even though the objects share a common method name.

At first blush, inheritance seems a little intimidating. However, if you run the program after setting a number of breakpoints at places where you feel you're unsure of what's going on, you can quickly understand what the code is doing. Single-stepping a program is a great way to get more depth of understanding of what a program is actually doing as it executes.

Extension Methods

Inheritance gives you a way to avoid duplicate code that could exist without the parent-child relationship. Of course, using inheritance requires that you have access to the source code for the class. Extension methods, however, allow you to extend the functionality of a class without the need for the source code.

You can think of extension methods as static methods that you can graft onto an existing class without having the source code for that class. The easiest way to understand what extension methods bring to the party is by example-like that in the next Try It Out.

Suppose you have a program that needs to verify that the user entered a valid Social Security number (SSN). A valid SSN is of the format ddd-dd-dddd, which means a field of three digits, a dash, two digits, another dash, and finally a four-digit sequence. What you want to do is extend theString class to add a method that checks for a valid SSN. Figure 16.4 shows a sample run of the program you are about to write. The next section presents an example of how you can use extension methods.

Figure 16.4 Using extension methods

image

Try It Out: Using Extension Methods (Chapter16ProgramInheritance.zip)

In the program shown in Figure 16.4, the user enters a SSN to be checked. The Verify button then calls code that determines whether the user entry forms a valid SSN or not and displays a message about the number entered. Although the format shown for the SSN in Figure 16.4 is the normal pattern for an SSN, sometimes you see SSN's entered as just a sequence of 9 digits with the dashes omitted. You should write your extension method to accept both forms.

To try out the extension program:

1. Create a new project in the normal way.

2. Create a user interface with two label, one textbox, and two button objects. You can also download the code from file Chapter16ProgramInheritance.zip.

3. Add the code from Listings 16-4 and 16-5.

How It WorksYou see the code for the extension method, as presented in Listing 16-4. You need to use the System.Text.RegularExpression reference because the code uses the regular expression parser library. In this example, you do use the namespace keyword to give the extension method its own identity (StringExtensionMethods).

Listing 16-4: Source Code for clsExtension. (clsExtension.cs)

using System;

using System.Text.RegularExpressions;

namespace StringExtensionMethods

{

public static class clsExtension

{

// For details on regualr expression pattern options, see:

// http://www.regular-expressions.info/reference.html

public static bool CheckValidSSN(this string str)

{

int len = str.Length;

Regex pattern = null;

// Is it xxx-xx-xxxx or xxxxxxxxx?

if (len == 11 || len == 9)

{

if (len == 9)

{ // Accept 9 digit characters

pattern = new Regex(@"\d{9}");

} else { // Accept ddd-dd-dddd

pattern = new Regex(@"\d{3}-\d{2}-\d{4}");

}

return pattern.IsMatch(str);

}

else

{

return false; // Not valid

}

}

}

}

The regular expression parser provides an easy way for you to look for patterns in a string. For example, the pattern

\d{3}-\d{2}-\d{4}

says to look for three decimal digits (the \d {3} component) followed by a dash, then two decimal digits, another dash, and finally four more decimal digits. (Recall that the ampersand (@) before a string means that any dashes are not to be interpreted as an escape sequence but as part of the string itself.) The call to IsMatch() passes in the pattern to be checked and returns true of the string matches the pattern or false otherwise. The URL address that appears in Listing 16-4 provides a concise explanation of most of the pattern options you can use with the regular expression parser.

The signature for CheckValidSSN() begins with the keyword this. Because this always refers to the current object, CheckValidSSN() has a reference to the string being checked by the call. You can see what this means in Listing 16-5.

Listing 16-5: Program to Use Extension Methods. (frmMain.cs)

using System;

using System.Windows.Forms;

using StringExtensionMethods;

public class frmMain : Form

{

private Label label1;

private Label lblResult;

private TextBox txtSSN;

private Button btnVerify;

private Button btnClose;

#region Windows Code

public frmMain()

{

InitializeComponent();

}

[STAThread]

public static void Main()

{

frmMain main = new frmMain();

Application.Run(main);

}

private void btnVerify_Click(object sender, EventArgs e)

{

string str = txtSSN.Text;

if (str.CheckValidSSN())

lblResult.Text = "Valid";

else

lblResult.Text = "Invalid";

}

private void btnClose_Click(object sender, EventArgs e)

{

Close();

}

}

At the top of the listing you can see

using StringExtensionMethods;

which makes the extension method available for use in the program. The rest of the code in the listing is nothing you haven't seen a dozen times before, except the call to the extension method:

if (str.CheckValidSSN())

lblResult.Text = "Valid";

else

lblResult.Text = "Invalid";

You can call your validation method using the same syntax as any other other string method.

Wait a minute.

The signature for CheckValidSSN() says it expects a string to be passed to it. Ah, yes, but the keyword this in the signature tells the method that it is the current object that is being referenced by the call. Therefore, the extension method already has access to the string being tested and doesn't need the string to be passed. The call returns true or false depending on the content of the string and the appropriate label displays.

Extension methods give you a convenient way to extend the functionality of a class without using inheritance. If you ever say: “I wish this class could do....”, you might consider writing an extension method. This is especially true if you write repetitive tests for string patterns or data values.

Summary

This chapter completed your study of the three pillars of object-oriented programming: encapsulation, inheritance, and polymorphism. Throughout the importance of encapsulating your data inside a class or methods has been stressed. In this chapter you learned how the protected access specifier permits you to encapsulate data, yet share it with derived classes as needed. You learned how the base class serves as a common denominator for related, yet distinct, classes. You also learned how polymorphism enables you to have each derived class react to messages in a way that is appropriate for it. Finally, you saw how extension methods can extend the functionality of a class by “tacking on” new methods without having to use inheritance.

Exercises

You can find the answers to the following exercises in Appendix A.

1. A golf club wants to set up a membership program that can send mail, e-mails, and voting ballots to its members. The membership consists of junior, regular, and senior golfing members, plus a social membership, which excludes golf. The problem is that junior and social members are not allowed to vote on club issues. Also, social members should not receive mailings about golf tournaments because they are not allowed to use the course, only the pool and restaurant facilities. If you were to write the program, how would you organize it?

2. Suppose you see the following line of code in a program:

public clsJunior(string addr, int status, decimal minimum)

: base(addr, status, minimum)

What can you tell me about the purpose of this line of code?

3. Some programmers don't like the protected access specifier. What do you think might be their reasons?

4. How would you prevent other programmers from extending one of your classes via inheritance?

5. Does inheritance bring anything to the party that you couldn't do without it?

What You Have Learned In This Chapter

Topic

Key Points

Inheritance

The ability to extend existing classes to accommodate the need fordifferent properties and methods without duplicating code.

Why inheritance is useful

It enables you to reuse and simplify your code.

Base (parent) class

The class from which other classes are derived.

Derived (child) class

The class(es) that inherit the properties and methods from the base class.

protected

An access specifier that enables properties and methods to be shared between base and derived classes.

Polymorphism

The capability for each derived class to respond in its own way to program events.

Extension methods

How to extend class functionality without using inheritance.