Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro CSharp 2008 And The .NET 3.5 Platform [eng]

.pdf
Скачиваний:
71
Добавлен:
16.08.2013
Размер:
22.5 Mб
Скачать

182 CHAPTER 5 DEFINING ENCAPSULATED CLASS TYPES

Note At the time of this writing, Microsoft has released as a Community Technology Preview (CTP) a tool named Sandcastle, which is similar in functionally to the open source NDoc utility. Check out http://www. sandcastledocs.com for more information (this URL is subject to change).

Visualizing the Fruits of Our Labor

At this point, you have created a fairly interesting class named Employee. If you are using Visual Studio 2008, you may wish to insert a new class diagram file (see Chapter 2) in order to view (and maintain) your class at design time. Figure 5-18 shows the completed Employee class type.

Figure 5-18. The completed Employee class

As you will see in the next chapter, this Employee class will function as a base class for a family of derived class types (WageEmployee, SalesEmployee, and Manager).

Source Code The EmployeeApp project can be found under the Chapter 5 subdirectory.

Summary

The point of this chapter was to introduce you to the role of the C# class type. As you have seen, classes can take any number of constructors that enable the object user to establish the state of the object upon creation. This chapter also illustrated several class design techniques (and related keywords). Recall that the this keyword can be used to obtain access to the current object, the static

CHAPTER 5 DEFINING ENCAPSULATED CLASS TYPES

183

keyword allows you to define fields and members that are bound at the class (not object) level, and the const keyword (and readonly modifier) allows you to define a point of data that can never change after the initial assignment.

The bulk of this chapter dug into the details of the first pillar of OOP: encapsulation. Here you learned about the access modifiers of C# and the role of type properties, partial classes, and XML code documentation. With this behind us, we are now able to turn to the next chapter where you will learn to build a family of related classes using inheritance and polymorphism.

C H A P T E R 6

Understanding Inheritance and

Polymorphism

The previous chapter examined the first pillar of OOP: encapsulation. At that time you learned how to build a single well-defined class type with constructors and various members (fields, properties, constants, read-only fields, etc.). This chapter will focus on the remaining two pillars of OOP: inheritance and polymorphism.

First, you will learn how to build families of related classes using inheritance. As you will see, this form of code reuse allows you to define common functionality in a parent class that can be leveraged (and possibly altered) by child classes. Along the way, you will learn how to establish a polymorphic interface into the class hierarchies using virtual and abstract members. We wrap up by examining the role of the ultimate parent class in the .NET base class libraries: System.Object.

The Basic Mechanics of Inheritance

Recall from the previous chapter that inheritance is the aspect of OOP that facilitates code reuse. Specifically speaking, code reuse comes in two flavors: classical inheritance (the “is-a” relationship) and the containment/delegation model (the “has-a” relationship). Let’s begin this chapter by examining the classical “is-a” inheritance model.

When you establish “is-a” relationships between classes, you are building a dependency between two or more class types. The basic idea behind classical inheritance is that new classes may leverage (and possibly extend) the functionality of existing classes. To begin with a very simple example, create a new Console Application project named BasicInheritance. Now assume you have designed a simple class named Car that models some basic details of an automobile:

// A simple base class. class Car

{

public readonly int maxSpeed; private int currSpeed;

public Car(int max)

{

maxSpeed = max;

}

public Car()

{

maxSpeed = 55;

}

185

186 CHAPTER 6 UNDERSTANDING INHERITANCE AND POLYMORPHISM

public int Speed

{

get { return currSpeed; } set

{

currSpeed += value;

if (currSpeed > maxSpeed)

{

currSpeed = maxSpeed;

}

}

}

}

Notice that the Car class is making use of encapsulation services to control access to the private currSpeed field using a public property named Speed. At this point you can exercise your Car type as follows:

static void Main(string[] args)

{

Console.WriteLine("***** Basic Inheritance *****\n");

// Make a Car type.

Car myCar = new Car(80); myCar.Speed = 50;

Console.WriteLine("My car is going {0} MPH", myCar.Speed); Console.ReadLine();

}

Specifying a Class Type’s Parent Class

Now assume you wish to build a new class named MiniVan. Like a basic Car, you wish to define the MiniVan class to support a maximum speed, current speed, and a property named Speed to allow the object user to modify the object’s state. Clearly, the Car and MiniVan classes are related; in fact we can say that a MiniVan is-aCar. The “is-a” relationship (formally termed classical inheritance) allows you to build new class definitions that extend the functionality of an existing class.

The existing class that will serve as the basis for the new class is termed a base or parent class. The role of a base class is to define all the common data and members for the classes that extend it. The extending classes are formally termed derived or child classes. In C#, we make use of the colon operator on the class definition to establish an “is-a” relationship between classes:

// MiniVan 'is-a' Car. class MiniVan : Car

{

}

So, what have we gained by extending our MiniVan from the Car base class? Simply put, MiniVan objects now have access to each public member defined within the parent class. Given the relation between these two class types, we could now make use of the MiniVan type like so:

static void Main(string[] args)

{

Console.WriteLine("***** Basic Inheritance *****\n");

...

// Make a MiniVan type.

MiniVan myVan = new MiniVan(); myVan.Speed = 10;

Console.WriteLine("My van is going {0} MPH",

CHAPTER 6 UNDERSTANDING INHERITANCE AND POLYMORPHISM

187

myVan.Speed);

Console.ReadLine();

}

Notice that although we have not added any members to the MiniVan class, we have direct access to the public Speed property of our parent class, and have thus reused code. Recall, however, that encapsulation is preserved; therefore the following code results in a compiler error:

static void Main(string[] args)

{

Console.WriteLine("***** Basic Inheritance *****\n");

...

//Make a MiniVan type.

MiniVan myVan = new MiniVan(); myVan.Speed = 10;

Console.WriteLine("My van is going {0} MPH", myVan.Speed);

//Error! Can't access private members using an object reference! myVan.currSpeed = 55;

Console.ReadLine();

}

On a related note, if the MiniVan defined its own set of members, it would not be able to access any private member of the Car base class:

// MiniVan derives from Car. class MiniVan : Car

{

public void TestMethod()

{

//OK! Can access public members

//of a parent within a derived type.

Speed = 10;

//Error! Cannot access private

//members of parent within a derived type. currSpeed = 10;

}

}

Regarding Multiple Base Classes

Speaking of base classes, it is important to keep in mind that the .NET platform demands that a given class have exactly one direct base class. It is not possible to create a class type that directly derives from two or more base classes (this technique [which is supported in other C-based languages, such as unmanaged C++] is known as multiple inheritance, or simply MI):

//Illegal! The .NET platform does not allow

//multiple inheritance for classes!

class WontWork

: BaseClassOne, BaseClassTwo

{}

As you will see in Chapter 9, the .NET platform does allow a given class (or structure) type to implement any number of discrete interfaces. In this way, a C# type can exhibit a number of behaviors while avoiding the complexities associated with MI. On a related note, while a class can have

188 CHAPTER 6 UNDERSTANDING INHERITANCE AND POLYMORPHISM

only one direct base class, it is permissible for an interface to directly derive from multiple interfaces. Using this technique, you can build sophisticated interface hierarchies that model complex behaviors (again, see Chapter 9).

The sealed Keyword

C# supplies another keyword, sealed, that prevents inheritance from occurring. When you mark a class as sealed, the compiler will not allow you to derive from this type. For example, assume you have decided that it makes no sense to further extend the MiniVan class:

// This class cannot be extended! sealed class MiniVan : Car

{

}

If you (or a teammate) were to attempt to derive from this class, you would receive a compiletime error:

//Error! Cannot extend

//a class marked with the sealed keyword! class DeluxeMiniVan

:MiniVan

{}

Most often, sealing a class makes the best sense when you are designing a utility class. For example, the System namespace defines numerous sealed classes. You can verify this for yourself by opening up the Visual Studio 2008 Object Browser (via the View menu) and selecting the System.String type defined within the mscorlib.dll assembly. Notice in Figure 6-1 the use of the sealed keyword highlighted in the Summary window.

Figure 6-1. The base class libraries define numerous sealed types.

Thus, just like the MiniVan, if you attempted to build a new class that extends System.String, you will receive a compile-time error:

//Another error! Cannot extend

//a class marked as sealed! class MyString

: String

{}

CHAPTER 6 UNDERSTANDING INHERITANCE AND POLYMORPHISM

189

Note In Chapter 4 you learned that C# structures are always implicitly sealed (see Table 4-3). Therefore, you can never derive one structure from another structure, a class from a structure or a structure from a class.

As you would guess, there are many more details to inheritance that you will come to know during the remainder of this chapter. For now, simply keep in mind that the colon operator allows you to establish base/derived class relationships, while the sealed keyword prevents inheritance from occurring.

Note C# 2008 introduces the concept of extension methods. As you will see in Chapter 13, this technique makes it possible to add new functionality to precompiled types (including sealed types) within your current project.

Revising Visual Studio Class Diagrams

Back in Chapter 2, I briefly mentioned that Visual Studio 2008 allows you to establish base/derived class relationships visually at design time. To leverage this aspect of the IDE, your first step is to include a new class diagram file into your current project. To do so, access the Project Add New Item menu option and select the Class Diagram icon (in Figure 6-2, I renamed the file from

ClassDiagram1.cd to Cars.cd).

Figure 6-2. Inserting a new class diagram

Once you click the Add button, you will be presented with a blank designer surface. To add types to a class designer, simply drag each file from the Solution Explorer window onto the surface. When you do so, the IDE responds by automatically including all types on the designer surface.

190CHAPTER 6 UNDERSTANDING INHERITANCE AND POLYMORPHISM

Realize that if you delete an item from the visual designer, this will not delete the associated source code, but simply take it off the designer surface. Our current class hierarchy is shown in Figure 6-3.

Figure 6-3. The visual designer of Visual Studio

Note As a shortcut, if you wish to automatically add all of your project’s types to a designer surface, select the Project node within the Solution Explorer and click the View Class Diagram button in the upper right of the Solution Explorer window.

Beyond simply displaying the relationships of the types within your current application, recall from Chapter 2 that you can also create brand new types (and populate their members) using the Class Designer toolbox and Class Details window. If you wish to make use of these visual tools during the remainder of the book, feel free. However, always make sure you analyze the generated code so you have a solid understanding of what these tools have done on your behalf.

Source Code The BasicInheritance project is located under the Chapter 6 subdirectory.

The Second Pillar: The Details of Inheritance

Now that you have seen the basic syntax of inheritance, let’s create a more complex example and get to know the numerous details of building class hierarchies. To do so, we will be reusing the Employee class we designed in Chapter 5. To begin, create a brand new C# Console Application named Employees. Next, activate the Project Add Existing Item menu option and navigate to the location of your Employee.cs and Employee.Internals.cs files. Select each of them (via a Ctrl-click) and click the OK button. Visual Studio 2008 responds by copying each file into the current project.

Before we start to build derived classes, you have one detail to attend to. Because the Employee class was created in a project named EmployeeApp, the type has been wrapped within an identically named .NET namespace scope. Chapter 15 will examine namespaces in detail; however for simplicity, rename the current namespace (in both file locations) to Employee in order to match your new project name:

CHAPTER 6 UNDERSTANDING INHERITANCE AND POLYMORPHISM

191

// Be sure to change the namespace name in both files! namespace Employees

{

///<summary>

///This class represents an Employee.

///</summary>

partial class Employee {...}

}

Our goal is to create a family of classes that model various types of employees in a company. Assume that you wish to leverage the functionality of the Employee class to create two new classes (SalesPerson and Manager). The class hierarchy we will be building initially looks something like what you see in Figure 6-4.

Figure 6-4. The initial Employees hierarchy

As illustrated in Figure 6-4, you can see that a SalesPerson “is-a” Employee (as is a Manager). Remember that under the classical inheritance model, base classes (such as Employee) are used to define general characteristics that are common to all descendents. Subclasses (such as SalesPerson and Manager) extend this general functionality while adding more specific behaviors.