Defining Classes - Classes - C# 24-Hour Trainer (2015)

C# 24-Hour Trainer (2015)

Section IV

Classes

The lessons in Section III focus on C# programming statements. They explain how to make decisions with if and switch statements, repeat program steps with loops, reuse code with methods, and catch exceptions.

Methods are particularly useful for programming at a higher level because they let you encapsulate complex behaviors in a tightly wrapped package. For example, you might write a CalculateGrade method that determines a student's grades. This method can hide all of the details of how grades are calculated. (Are tests graded on a curve? Is the grade a weighted average of tests and homework assignments? How much is attendance worth?) The main program only needs to know how to call the method, not how it works.

Classes provide another even more powerful method for abstracting complex entities into manageable packages. For example, a Student class might embody the idea of a student and include basic information (name, address, phone), the courses that the student is taking, grades (test scores, homework grades), and even attendance. It could also include methods such as CalculateGrade for manipulating the Student data.

The lessons in this section explain classes. They explain how you can build classes, make one class inherit the capabilities of another, and make a class override the features of its parent class.

· Lesson 23: Defining Classes

· Lesson 24: Initializing Objects

· Lesson 25: Fine-Tuning Classes

· Lesson 26: Overloading Operators

· Lesson 27: Using Interfaces

· Lesson 28: Making Generic Classes

Lesson 23

Defining Classes

This book hasn't emphasized the fact, but you've been working with classes since the very beginning. The very first program you created in Lesson 1 included several classes such as the program's main form and some behind-the-scenes classes that help get the program running. Since then, you've used all kinds of control classes, the MessageBox class, the Array class, collection classes, and more. You can even treat primitive data types such as int and string as classes under some circumstances.

In this lesson you learn how to create your own classes. You learn how to define a class and give it properties, methods, and events to make it useful.

What Is a Class?

A class defines a type of object. It defines the properties, methods, and events provided by its type of object. After you define a class, you can make as many instances of that class as you like.

For example, the Button class defines the properties and behaviors of a button. You can create any number of instances of Buttons and place them on your forms.

You can think of a class as a blueprint for making objects. When you create an instance of the class, you use the blueprint to make an object that has the properties and behaviors defined by the class.

You can also think of a class as a cookie cutter. Once you've created the cookie cutter, you can make any number of cookies that all have the same shape.

Classes are very similar to the structures described in Lesson 17, and many of the techniques you learned there apply here as well. For example, you can give a class fields that an instance of the class can use to perform calculations.

Several important differences exist between structures and classes, but one of the most important is that structures are value types while classes are reference types. Perhaps the most confusing consequence of this is that when you assign structure variable A equal to structure variable B, A becomes a copy of B. In contrast, if you assign class variable C equal to class variable D, then variable C now points to the same object that variable D does.

For a more detailed discussion of some of these differences, see the section “Structures Versus Classes” in Lesson 17.

The rest of this lesson focuses on classes and doesn't talk specifically about structures.

NOTE

Note that the same techniques apply to structures and classes. For example, structures have the same benefits as classes described in the following section. Just because I'm describing them here doesn't mean I'm trying to imply that classes are better because they have these advantages and structures don't.

Class Benefits

The biggest benefit of classes is encapsulation. A well-designed class hides its internal workings from the rest of the program so the program can use the class without knowing how the class works.

For example, suppose you build a Turtle class to represent a turtle crawling across the screen drawing lines as it moves. The class would need properties such as X, Y, and Direction to define the Turtle's location and direction. It might also provide methods such as Turnto make it change direction and Move to make it move.

The Turtle class needs to know how to draw the Turtle's path as it moves, but the main program doesn't need to know how it works. It doesn't need to know about Graphics objects, Pens, or the trigonometric functions the Turtle uses to figure out where to go. The main program only needs to know how to set the Turtle's properties and call its methods.

Some other benefits of classes (and structures) include:

· Grouping data and code—The code that makes a Turtle move is right in the same object as the data that determines the Turtle's position and direction.

· Code reuse—You only need to write the code for the Turtle class once and then all instances of the class get to use it. You get even more code reuse through inheritance, which is described in the section “Inheritance” later in this lesson.

· Polymorphism—Polymorphism means you can treat an object as if it were from another class as long as it inherits from that class. For example, a Student is a type of Person so you should be able to treat a Student object as if it were either a Student or a Person. The section “Polymorphism” later in this lesson describes this further.

Making a Class

Now that you know a bit about what classes are good for, it's time to learn how to build one.

Making a class in C# is simple. Open the Project menu and select Add Class. Give the class a good name and click Add.

Initially the class looks something like the following:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

namespace MyProgram

{

class Employee

{

}

}

Here MyProgram is your program's default namespace, which is normally the same as the program's name. It is used as the namespace for all of the forms and other classes that you add to the program.

Employee is the name that I gave the class in this example.

At this point, the class doesn't contain any data or methods so it can't do anything. You can write code to create an instance of the class, but it will just sit there. To make the class useful, you need to add properties, methods, and events:

· Properties are values associated with a class. For example, an Employee class might define FirstName, LastName, and EmployeeId properties.

· Methods are actions that an object can perform. For example, an Employee class might provide a CalculateBonus method that calculates the employee's end-of-year bonus based on performance during the year.

· Events are raised by the class to tell the rest of the program that something interesting happened, sort of like raising a flag to draw attention to something. For example, the Employee class might raise a TooManyHours event if the program tried to assign an employee more than 40 hours of work in a week.

Properties, methods, and events allow a program to control and interact with objects. The following sections explain how you can add properties, methods, and events to your classes.

Properties

If you give a class a public variable, other pieces of code can get and set that variable values. This kind of variable is called a field. A field is similar to a property but it has one big disadvantage: it provides unrestricted access to its value. That means other parts of the program could dump any garbage into the field without the class being able to stop them.

In contrast, a class implements a property by using accessor methods that can include code to protect the class from garbage values. You learn more about this as you see how to build properties.

The following sections describe the two most common approaches for implementing properties: auto-implemented properties and backing fields.

Auto-Implemented Properties

The easiest way to make a property is to use an auto-implemented property. The syntax for an auto-implemented property is:

accessibility dataType Name { get; set; }

Here accessibility determines what code can use the property. It can be public, private, and so on. The dataType determines the property's data type and Name determines its name. The get and set keywords indicate that other code should be able to get and set the property's value.

NOTE

You can omit the set clause to create a read-only property.

The following code creates a simple property named FirstName of type string:

public string FirstName { get; set; }

Backing Fields

When you make an auto-implemented property, C# automatically generates accessors that let you get and set the property's value. You can use those accessors without needing to know the details of how they work.

When you make a property that is not auto-implemented, you need to write the accessors yourself.

The following shows the basic syntax used to define a property that is not auto-implemented:

accessibility dataType Name

{

get

{

...getCode…

}

set

{

...setCode…

}

}

Here accessibility, dataType, and Name are the same as before. The getCode and setCode are the pieces of code that get and set the property's value somehow.

One common way to implement this kind of property is with a backing field. A backing field is a field that stores data to represent the property. The getCode and setCode use the backing field to get and set the property's value.

The following C# code shows a version of the Direction property stored in the backing field named direction:

// The Turtle's direction in degrees.

private int direction = 0; // Backing field.

public int Direction

{

get { return direction; }

set { direction = value; }

}

The code starts by defining the field direction to hold the property's value. The field is private so only the code inside the class can see it.

The property's get accessor simply returns the value of direction.

The property's set accessor saves a new value in the backing field direction. The new value that the calling code is trying to assign to the property is stored in a parameter named value. This parameter is a bit odd because it isn't declared anywhere. The set accessor implicitly defines value and can use it.

The preceding code simply copies values in and out of the backing field, so why didn't you just make the backing field public and not bother with a property? There are several reasons.

First, a property hides its details from the outside world, increasing the class's encapsulation. As far as the outside world is concerned, a description of the Direction property tells you what is stored (the direction in degrees) but not how it is stored (as an integer value in degrees).

This example stores the direction in degrees, but suppose you decided that the class would work better if you stored the direction in radians. If Direction is a field, then any code that uses it would now break because it is using degrees. If you use accessors, they can translate between degrees and radians as needed so the code outside the class doesn't need to know that anything has changed.

The following code shows a new version of the Direction property that stores the value in radians. As far as the code outside the class is concerned, nothing has changed and that code can still work in degrees.

// The Turtle's direction in radians.

private double direction = 0; // Backing field.

public int Direction

{

get { return (int)(direction * 180 / Math.PI); }

set { direction = value * Math.PI / 180; }

}

You can also add validation code to property accessors. For example, suppose the Direction property represents an angle in degrees and you only want to allow values between 0 and 359. The following code asserts that the new value is between 0 and 359 degrees. The program can continue correctly if the value is outside of this range so the code uses Debug.Assert instead of throwing an exception:

// The Turtle's direction in degrees.

private int direction = 0; // Backing field.

public int Direction

{

get { return direction; }

//set { direction = value; }

set

{

Debug.Assert((value >= 0) && (value <= 359),

"Direction should be between 0 and 359 degrees");

direction = value;

}

}

Property accessors also give you a place to set breakpoints if something goes wrong. For example, if you know that some part of your program is setting a Turtle's Direction to 45 when it should be setting it to 60 but you don't know where, you could set a breakpoint in the set accessor to see where the change is taking place.

Try It

Because classes are important and somewhat confusing, this lesson includes three Try Its. In this first Try It, you create a simple Person class with FirstName, LastName, City, Street, and Zip properties that have some simple validations. You also build a simple test application shown in Figure 23.1.

Screenshot of Person Class window displaying a filled-up form excluding the First Name with Create button on the right and a popped-up error box noting the blank parameter.

Figure 23.1

Lesson Requirements

In this lesson, you:

· Build the program shown in Figure 23.1.

· Create a Person class.

· Make auto-implemented properties for Street, City, State, and Zip.

· Make FirstName and LastName properties that use backing fields. Add validation code to their set accessors to prevent you from setting FirstName or LastName to a null or blank value.

Step-by-Step

· Build the program shown in Figure 23.1.

1. This is reasonably straightforward.

· Create a Person class.

1. Use the Project menu's Add Class item. Name the class Person.

· Make auto-implemented properties for Street, City, State, and Zip.

1. You can use code similar to the following:

2. // Auto-implemented properties.

3. public string Street { get; set; }

4. public string City { get; set; }

5. public string State { get; set; }

public string Zip { get; set; }

· Make FirstName and LastName properties that use backing fields. Add validation code to their set accessors to prevent you from setting FirstName or LastName to a null or blank value.

1. The following code shows how you might implement the FirstName property. The code for the LastName property is similar.

2. // FirstName property.

3. private string firstName = "";// Backing field.

4. public string FirstName

5. {

6. get

7. {

8. return firstName;

9. }

10. set

11. {

12. if (value == null)

13. throw new ArgumentOutOfRangeException("FirstName",

14. "Person.FirstName cannot be null.");

15. if (value.Length < 1)

16. throw new ArgumentOutOfRangeException("FirstName",

17. "Person.FirstName cannot be blank.");

18. }

}

Methods

A method is simply a piece of code in the class that other parts of the program can execute. The following method shows how the Turtle class might implement its Move method:

// Make the Turtle move the indicated distance

// in its current direction.

public void Move(int distance)

{

// Calculate the new position.

double radians = Direction * Math.PI / 180;

int newX = (int)(X + Math.Cos(radians) * distance);

int newY = (int)(Y + Math.Sin(radians) * distance);

// Draw to the new position.

using (Graphics gr = Graphics.FromImage(Canvas))

{

gr.DrawLine(Pens.Blue, X, Y, newX, newY);

}

// Save the new position.

X = newX;

Y = newY;

}

The method takes as a parameter the distance it should move. It uses the Turtle's current position and direction to figure out where this move will finish. It uses some graphics code to draw a line from the current position to the new one (don't worry about the details) and finishes by saving the new position.

Events

Events let the class tell the rest of the program that something interesting is happening. For example, if a BankAccount object's balance falls below 0, it could raise an AccountOverdrawn event to notify the main program.

Declaring an event in C# is a bit tricky because you first need to understand delegates.

Delegates

A delegate is a data type that can hold a specific kind of method. For example, you could make a delegate type that represents methods that take no parameters and return a double. You could then declare a variable of that type and save a method in it.

Confusing? You bet!

The key to understanding delegates is to remember that a delegate type is a new data type just like a string or int. The difference is that a variable with a delegate type holds a method, not a simple value like “Hello” or 27.

The Delegates example program, which is part of this lesson's download on the book's website, provides a simple example. The program uses four steps to demonstrate delegates: declare the delegate type, create variables of that type, initialize the variables, and use the variables' values.

First the program defines a delegate type:

// Define a delegate type that takes no parameters and returns nothing.

private delegate void DoSomethingMethod();

The declaration begins with the accessibility keyword private and then the keyword delegate to tell C# that it is defining a delegate type. The rest of the declaration gives the delegate type's name DoSomethingMethod. It also indicates that instances of this type must refer to methods that take no parameters and return nothing (void).

Now that it has defined the delegate type, the code declares three variables of that type. Each of the variables can hold a reference to a method that takes no parameters and returns nothing:

// Declare three DoSomethingMethod variables.

private DoSomethingMethod method1, method2, method3;

Next the program defines two methods that match the delegate's definition:

// Define some methods that have the delegate's type.

private void SayHi()

{

MessageBox.Show("Hi");

}

private void SayClicked()

{

MessageBox.Show("Clicked");

}

When the program starts, the following Load event handler sets the variables method1, method2, and method3 so they point to these two methods. Notice that the code makes method1 and method3 point to the same method, SayHi:

// Initialize the delegate variables.

private void Form1_Load(object sender, EventArgs e)

{

method1 = SayHi;

method2 = SayClicked;

method3 = SayHi;

}

At this point, the program has defined the delegate type, created three variables of that type, and initialized those variables so they refer to the SayHi and SayClicked methods. Now the program is ready to use the variables.

The program displays three buttons. When you click them, the following event handlers execute. Each button simply invokes the method referred to by one of the delegate variables.

// Invoke the method stored in the delegates.

private void method1Button_Click(object sender, EventArgs e)

{

method1();

}

private void method2Button_Click(object sender, EventArgs e)

{

method2();

}

private void method3Button_Click(object sender, EventArgs e)

{

method3();

}

When it executes, a Button's event handler doesn't “know” what method is stored in its variable. For example, the last Button invokes method3 without knowing which “real” method will execute.

This isn't an extremely practical program, and it's hard to imagine a situation where you would just want buttons to invoke the methods stored in different delegates. However, this example is much simpler than many programs that use delegates so it's worth studying before you look at more realistic examples.

Event Handler Delegates

Now that you know a bit about delegates, you can learn how to use them to make an event.

First, in the class that will raise the event, declare a delegate type to define the event handler. Usually developers end the delegate's name with EventHandler to make it obvious what the delegate represents.

By convention, event handlers usually take two parameters named sender and e. The sender parameter is an object that contains a reference to whatever object is raising the event. The e parameter contains data specific to the event. Often you will define a class to provide that information and the parameter e will be of that class.

For example, suppose you want the Turtle class to raise an OutOfBounds event to tell the program that it is trying to move the Turtle off the drawing area. You want the parameter e to tell the program the X and Y coordinates where the Turtle was trying to move.

In that case, you could use the following TurtleOutOfBoundsEventArgs class to store the X and Y coordinates:

// The TurtleOutOfBoundsEventArgs data type.

public class TurtleOutOfBoundsEventArgs

{

// Where the Turtle would stop if

// this were not out of bounds.

public int X { get; set; }

public int Y { get; set; }

};

The following code shows how the Turtle class could declare its OutOfBoundsEventHandler delegate:

// Declare the OutOfBound event's delegate.

public delegate void OutOfBoundsEventHandler(

object sender, TurtleOutOfBoundsEventArgs e);

Next the class must declare the actual event to tell C# that the class will provide this event. The declaration should begin with an accessibility keyword (public, private, and so on) followed by the keyword event. Next it should give the event handler's delegate type. It finishes with the event's name.

The following code declares the OutOfBounds event, which is handled by event handlers of type OutOfBoundsEventHandler:

// Declare the OutOfBounds event.

public event OutOfBoundsEventHandler OutOfBounds;

The final piece of code that you need to add to the class is the code that raises the event. This code simply invokes the event handler, passing it any parameters that it should receive.

Before it raises the event, however, the code should verify that some other piece of code has registered to receive the event. The code does that by checking whether the event is null. (This syntax seems a bit strange to me. The code looks like it is checking that anevent is null when really it's asking whether another piece of code has asked to receive the event. This is just the syntax used by C#.)

The following code raises the Turtle class's OutOfBounds event:

if (OutOfBounds != null)

{

TurtleOutOfBoundsEventArgs args = new TurtleOutOfBoundsEventArgs();

args.X = newX;

args.Y = newY;

OutOfBounds(this, args);

}

If OutOfBounds is not null (in other words, some other code wants to receive the event), the code creates a new TurtleOutOfBoundsEventArgs object, initializes it to indicate the point the Turtle was trying to move to, and then calls OutOfBounds, passing it the object raising the event and the TurtleOutOfBoundsEventArgs object.

A class uses code to decide when to raise the event. The following code shows how the Turtle class raises its event when the Move method tries to move beyond the edge of the Turtle's Bitmap. The bold code determines whether the Turtle is moving out of bounds and raises the event if necessary.

// Make the Turtle move the indicated distance

// in its current direction.

public void Move(int distance)

{

// Calculate the new position.

double radians = Direction * Math.PI / 180;

int newX = (int)(X + Math.Cos(radians) * distance);

int newY = (int)(Y + Math.Sin(radians) * distance);

// See if the new position is off the Bitmap.

if ((newX < 0) || (newY < 0) ||

(newX >= Canvas.Width) || (newY >= Canvas.Height))

{

// Raise the OutOfBounds event, passing

// the event handler the new coordinates.

if (OutOfBounds != null)

{

TurtleOutOfBoundsEventArgs args =

new TurtleOutOfBoundsEventArgs();

args.X = newX;

args.Y = newY;

OutOfBounds(this, args);

}

return;

}

// Draw to the new position.

using (Graphics gr = Graphics.FromImage(Canvas))

{

gr.DrawLine(Pens.Blue, X, Y, newX, newY);

}

// Save the new position.

X = newX;

Y = newY;

}

There's still one piece missing to all of this. The main program must register to receive the OutOfBound event or it won't know when the Turtle has raised it.

When the Turtle program starts, its Form_Load event handler executes the following code. This adds the Turtle_OutOfBounds method as an event handler for the MyTurtle object's OutOfBounds event. Now if the MyTurtle object raises its event, the program'sTurtle_OutOfBounds event handler executes.

// Register to receive the OutOfBounds event.

MyTurtle.OutOfBounds += Turtle_OutOfBounds;

NOTE

You can remove an event handler by using code like this:

MyTurtle.OutOfBounds -= Turtle_OutOfBounds;

The following code shows the Turtle program's Turtle_OutOfBounds event handler:

// Handle the OutOfBounds event.

private void Turtle_OutOfBounds(object sender, Turtle.TurtleOutOfBoundsEventArgs e)

{

MessageBox.Show(string.Format("Oops! ({0}, {1}) is out of bounds.",

e.X, e.Y));

}

Try It

In this second Try It in the lesson, you create a BankAccount class. You give it a Balance property and two methods, Credit and Debit. The Debit method raises an Overdrawn event if a withdrawal would give the account a negative balance.

You also build the test application shown in Figure 23.2.

Bank Account dialog box presenting Amount and Balance fields with Credit and Debit buttons. Overlaying it is a prompt indicating “Insufficient funds.”

Figure 23.2

Lesson Requirements

In this lesson, you:

· Build the program shown in Figure 23.2.

· Create a BankAccount class. Give it a Balance property.

· Add Debit and Credit methods to add and remove money from the account.

· Define the AccountOverdrawnArgs class to pass to event handlers.

· Define the OverdrawnEventHandler delegate type.

· Declare the Overdrawn event itself.

· Make the Debit method raise the event when necessary.

· In the main program, register to receive the Overdrawn event so it can display a message box.

· In the main program, make the Credit and Debit buttons add and remove money from the bank account, respectively.

NOTE

You can download the code and resources for this lesson from the website at www.wrox.com/go/csharp24hourtrainer2e.

Hints

· This example doesn't do anything special with the Balance property so you can make it auto-implemented.

· Make the main form create an instance of the BankAccount class to manipulate.

Step-by-Step

· Build the program shown in Figure 23.2.

1. This is reasonably straightforward.

· Create a BankAccount class. Give it a Balance property.

1. Use code similar to the following:

2. // The account balance.

public decimal Balance { get; set; }

· Add Debit and Credit methods to add and remove money from the account.

1. Start with code similar to the following. You'll modify the Debit method later to raise the Overdrawn event.

2. // Add money to the account.

3. public void Credit(decimal amount)

4. {

5. Balance += amount;

6. }

7. // Remove money from the account.

8. public void Debit(decimal amount)

9. {

10. Balance -= amount;

}

· Define the AccountOverdrawnArgs class to pass to event handlers.

1. Use code similar to the following:

2. // Define the OverdrawnEventArgs type.

3. public class OverdrawnEventArgs

4. {

5. public decimal currentBalance, invalidBalance;

}

· Define the OverdrawnEventHandler delegate type.

1. Use code similar to the following:

2. // Define the OverdrawnEventHandler delegate type.

3. public delegate void OverdrawnEventHandler(

object sender, OverdrawnEventArgs args);

· Declare the Overdrawn event itself.

1. Use code similar to the following:

2. // Declare the Overdrawn event.

public event OverdrawnEventHandler Overdrawn;

· Make the Debit method raise the event when necessary.

1. Modify the initial version of the method so it raises the event when necessary. Use code similar to the following:

2. // Remove money from the account.

3. public void Debit(decimal amount)

4. {

5. // See if there is enough money.

6. if (Balance < amount)

7. {

8. // Not enough money. Raise the Overdrawn event.

9. if (Overdrawn != null)

10. {

11. OverdrawnEventArgs args = new OverdrawnEventArgs();

12. args.currentBalance = Balance;

13. args.invalidBalance = Balance - amount;

14. Overdrawn(this, args);

15. }

16. }

17. else

18. {

19. // There's enough money.

20. Balance -= amount;

21. }

}

· In the main program, make the Credit and Debit buttons add and remove money from the bank account, respectively.

1. Use code similar to the following:

2. // Add money to the account.

3. private void creditButton_Click(object sender, EventArgs e)

4. {

5. // Add the money.

6. decimal amount = decimal.Parse(amountTextBox.Text);

7. MyAccount.Credit(amount);

8. // Display the current balance.

9. balanceTextBox.Text = MyAccount.Balance.ToString("C");

10. }

11. // Remove money from the account.

12. private void debitButton_Click(object sender, EventArgs e)

13. {

14. // Remove the money.

15. decimal amount = decimal.Parse(amountTextBox.Text);

16. MyAccount.Debit(amount);

17. // Display the current balance.

18. balanceTextBox.Text = MyAccount.Balance.ToString("C");

}

Inheritance

Often when you build one class, you end up building a bunch of other closely related classes. For example, suppose you're building a program that models your company's organization. You might build an Employee class to represent employees. After a while, you may realize that there are different kinds of employees: managers, supervisors, project leaders, and so forth.

You could build each of those classes individually but you'd find that these classes have a lot in common. They all probably have FirstName, LastName, Address, EmployeeId, and other properties. Depending on the kinds of operations you need the objects to perform, you might also find that they share a lot of methods: ScheduleVacation, PrintTimesheet, RecordHours, and so forth. Although you could build each of these classes individually, you would end up duplicating a lot of code in each class to handle these common features.

Fortunately, C# allows you to make one class inherit from another and that lets them share common code. When you make one class inherit from another one, you derive the new class from the existing class. In that case, the new class is called the child class and the class from which it inherits is called the parent class.

In this example, you could build a Person class with properties that all people have: FirstName, LastName, Street, City, State, Zip, Email, and Phone. You could then derive the Employee class from Person and add the new property EmployeeId.

Next you could derive the Manager class from Employee (because all Managers are also Employees) and add new manager-related properties such as DepartmentName and DirectReports.

Syntactically, to make a class that inherits from another you add a colon and the parent class's name after the child class's declaration. For example, the following code defines the Manager class, which inherits from Employee. In addition to whatever features theEmployee class provides, Manager adds new DepartmentName and DirectReports properties:

class Manager : Employee

{

public string DepartmentName { get; set; }

public List<Employee> DirectReports = new List<Employee>();

}

NOTE

Note that C# only supports single inheritance. That means a class can inherit from at most one parent class. For example, if you define a House class and a Boat class, you cannot make a HouseBoat class that inherits from both.

Polymorphism

Polymorphism is a rather confusing concept that basically means a program can treat an object as if it were any class that it inherits. Another way to think of this is that polymorphism lets you treat an object as if it were any of the classes that it is. For example, anEmployee is a kind of Person so you should be able to treat an Employee as a Person.

Note that the reverse is not true. A Person is not necessarily an Employee (it could be a Customer or some other unrelated person), so you can't necessarily treat a Person as an Employee.

For a more detailed example, suppose you make the Person, Employee, and Manager classes and they inherit from each other in the natural progression: Employee inherits from Person and Manager inherits from Employee.

Now suppose you write a SendEmail method that takes a Person as a parameter and sends a message to the e-mail address stored in the Person's Email property. Employee inherits from Person so you should be able to pass an Employee into this method and the method should be able to treat it as a Person. This makes intuitive sense because an Employee is a Person, just a particular kind of Person.

Similarly, Manager inherits from Employee so a Manager is a kind of Employee. If an Employee is a kind of Person and a Manager is a kind of Employee, then a Manager must also be a kind of Person, so the same method should be able to take a Manager as its parameter.

Try It

In the final Try It of this lesson, you get to experiment with classes, inheritance, and polymorphism. You build Person, Employee, and Manager classes. To test the classes, you build a simple program that creates instances of each class and passes them to a method that takes a Person as a parameter.

Lesson Requirements

In this lesson, you:

· Create a Person class with properties FirstName, LastName, Street, City, State, Zip, Email, and Phone. Give the Person class a GetAddress method that returns the Person's name and address properties as a string in the format:

1. Alice Archer

2. 100 Ash Ave

3. Bugsville CO 82010

· Derive an Employee class from Person. Add the properties EmployeeId and MailStop.

· Derive a Manager class from Employee. Add a DepartmentName property and a DirectReports property of type List<Employee>. Make a GetDirectReportsList method that returns the names of the Manager's Employees separated by newlines.

· Make the main program create two Employees named Alice and Bob, a Manager named Cindy who has Alice and Bob in her department, and a Person named Dan.

· Make a ShowAddress method that takes a Person as a parameter and displays the Person's address.

· On the main form, make buttons that call ShowAddress for each of the people, passing the method the appropriate object.

· Make a final button that displays Cindy's list of direct reports.

NOTE

You can download the code and resources for this lesson from the website at www.wrox.com/go/csharp24hourtrainer2e.

Hints

· This example doesn't do anything fancy with the class's properties so you can use auto-implemented properties.

· The ShowAddress method should take a Person parameter even though some of the objects it will be passed are Employees or Managers.

Step-by-Step

· Create a Person class with properties FirstName, LastName, Street, City, State, Zip, Email, and Phone. Give the Person class a GetAddress method that returns the Person's name and address properties as a string in the format:

1. Alice Archer

2. 100 Ash Ave

3. Bugsville CO 82010

4. Make a new Person class with code similar to the following:

5. class Person

6. {

7. public string FirstName { get; set; }

8. public string LastName { get; set; }

9. public string Street { get; set; }

10. public string City { get; set; }

11. public string State { get; set; }

12. public string Zip { get; set; }

13. // Display the person's address.

14. // A real application might print this on an envelope.

15. public string GetAddress()

16. {

17. return FirstName + " " + LastName +

18. "\n" + Street + "\n" + City +

19. " " + State + " " + Zip;

20. }

}

· Derive an Employee class from Person. Add the properties EmployeeId and MailStop.

1. Make the Employee class similar to the following:

2. class Employee : Person

3. {

4. public int EmployeeId { get; set; }

5. public string MailStop { get; set; }

}

· Derive a Manager class from Employee. Add a DepartmentName property and a DirectReports property of type List<Employee>. Make a GetDirectReportsList method that returns the names of the Manager's Employees separated by newlines.

1. Make the Manager class similar to the following:

2. class Manager : Employee

3. {

4. public string DepartmentName { get; set; }

5. public List<Employee> DirectReports = new List<Employee>();

6. // Return a list of this manager's direct reports.

7. public string GetDirectReportsList()

8. {

9. string result = "";

10. foreach (Employee emp in DirectReports)

11. {

12. result += emp.FirstName + " " + emp.LastName + "\n";

13. }

14. return result;

15. }

}

· Make the main program create two Employees named Alice and Bob, a Manager named Cindy who has Alice and Bob in her department, and a Person named Dan.

1. Because the program's buttons need to access the objects, these objects should be stored in class-level fields as in the following code:

2. // Define some people of various types.

3. private Person Dan;

4. private Employee Alice, Bob;

private Manager Cindy;

5. Add code to the main form's Load event handler to initialize the objects. The following code shows how the program might create Alice's Employee object:

6. // Make an Employee named Alice.

7. Alice = new Employee();

8. Alice.FirstName = "Alice";

9. Alice.LastName = "Archer";

10. Alice.Street = "100 Ash Ave";

11. Alice.City = "Bugsville";

12. Alice.State = "CO";

13. Alice.Zip = "82010";

14. Alice.EmployeeId = 1001;

Alice.MailStop = "A-1";

15.Creating and initializing the other objects is similar. The only odd case is adding Alice and Bob as Cindy's employees as in the following code:

16. Cindy.DirectReports.Add(Alice);

Cindy.DirectReports.Add(Bob);

· Make a ShowAddress method that takes a Person as a parameter and displays the Person's address.

1. Use code similar to the following:

2. // Display this Person's address.

3. private void ShowAddress(Person person)

4. {

5. MessageBox.Show(person.GetAddress());

}

· On the main form, make buttons that call ShowAddress for each of the people, passing the method the appropriate object.

1. Create the buttons' Click event handlers. The following code shows the event handler that displays Cindy's address:

2. private void cindyAddressButton_Click(object sender, EventArgs e)

3. {

4. ShowAddress(Cindy);

}

Note that the variable Cindy is a Manager but the ShowAddress method treats it as a Person. That's okay because Manager inherits indirectly from Person.

· Make a final button that displays Cindy's list of direct reports.

1. This method simply calls the Cindy object's GetDirectReportsList method and displays the result:

2. // Display Cindy's direct reports.

3. private void cindyReportsButton_Click(object sender, EventArgs e)

4. {

5. MessageBox.Show(Cindy.GetDirectReportsList());

}

Exercises

1. Write a program similar to the one shown in Figure 23.3 to manipulate complex numbers. When you enter the complex numbers' real and imaginary parts in the textboxes and click Calculate, the program should display the sum, difference, and product of the two complex numbers.Image described by surrounding text.

Figure 23.3

Make a ComplexNumber class with properties Real and Imaginary to hold a number's real and imaginary parts, respectively. Give the class AddTo, MultiplyBy, and SubtractFrom methods that combine the current ComplexNumber with another taken as a parameter and return the result as a new ComplexNumber.

Hints: Recall from school these equations for calculating with complex numbers:

(A + Bi) + (C + Di) = (A + C) + (B + D)i

(A + Bi) − (C + Di) = (A − C) + (B − D)i

(A + Bi) × (C + Di) = (A × C − B × D) + (A × D + B × C)i

For more review of complex numbers, see en.wikipedia.org/wiki/Complex_numbers or mathworld.wolfram.com/ComplexNumber.html.

2. [Games] Suppose you're writing a role-playing game and design classes to represent the player's class choices: fighter, magic-user, and rogue.

Hints:

· Give each class a few representative properties, but you don't need to include everything you would need to actually build the game.

· Use auto-implemented properties.

· Give each class a few methods that might make sense for the class but don't give them any code. (You may need to add a return statement if a method returns a value.)

· Make most properties strings instead of objects. For example, you can represent a weapon as a string holding the weapon's name (as in “sword”); you don't need to use some sort of Weapon or Sword class.

· Think about what the classes have in common and how you can avoid duplicating code.

3. Build Person and Student classes. Give the Student class (directly or via inheritance) typical name and address properties, plus a list to hold the courses (strings) that the Student is enrolled in. Also give the class an Enroll method that adds a course to the list.

Next make a user interface that lets the user add courses to a Student. After adding a new course, display the Student's courses in a ListBox. (Hint: The word class is a keyword used by C# so it's easier to use the word “course” instead when you're talking about enrollment.)

4. Copy the program you wrote for Exercise 3 and modify the Enroll method so it throws an ArgumentException if the program tries to enroll the student in the same course twice or if the student is already enrolled in six courses.

5. Copy the program you wrote for Exercise 4 and modify the Enroll method so it raises an Overenrolled event instead of throwing an exception if the student tries to enroll in more than six courses.

6. Sometimes you can use an event handler to tell the program about unusual circumstances and let the program decide whether to allow some action. For example, a form's FormClosing event handler can use its e.Cancel parameter to cancel the close and force the form to remain open.

Consider the program you wrote for Exercise 3. Under some circumstances, you may want to allow a student to enroll in more than six courses. (For example, students such as Hermione Granger who have time turners.) Copy that program and add an Allow field to the OverenrolledEventArgs class. Make the Student class initialize Allow to false and then invoke the event handlers.

Make the main program catch the event, display a message box asking the user whether it should allow the student to overenroll, and set Allow accordingly.

After the event handlers return, make the Student class allow the student to overenroll if Allow is true.

7. [Games, Hard] Most games that involve moving objects use sprites to represent those objects. A sprite is simply an instance of a class that represents the game object's position, velocity, colors, and other attributes. The goal is to move as much information about the objects as possible into the sprite class so the main program doesn't need to know about it.

Copy the bouncing ball program you built for Exercise 19-8 (or download the version available on the book's website) and modify it so it uses a Ball class to track balls. Hints:

· Add the directive using System.Drawing to the file that defines the Ball class.

· Give the Ball class the fields (or properties) X, Y, Vx, Vy, Width, Height, and Brush. Also give it a new ClientSize property of type Size.

· Give the Ball class an Initialize method that randomizes the Ball's properties. Hints:

§ Pass the main form's ClientSize into the Initialize method. Make the method save it in the Ball's ClientSize field.

§ Make a private static array called brushes that lists the brushes from which to pick randomly. (Making the array private means code outside of the Ball class cannot see it. Making it static means all instances of the Ball class share the same array, so they don't waste space by creating a new array for each Ball object.)

§ Make a private static Random object for the Ball instances to share. (This solves a tricky problem. When a program makes a Random object, it uses the system time to initialize itself by default. This program makes all of the Balls at the same time. That means if each Ball made its own Random object, they would all be initialized at almost exactly the same time so the Random objects would all produce the same sequence of “random” values. The result would be a bunch of Balls with the same positions, velocities, and colors. Using the static keyword makes all of the Balls share the same Random object so they get different “random” values. To see the problem, just remove the static keyword from the Random object's declaration.)

· Give the Ball class a Move method that updates the Ball's position. If the Ball hits a wall, raise a HitWall event.

· Give the Ball class a Draw method that takes a Graphics object as a parameter and draws the ball on it.

· Make the form's code use the Ball methods to initialize, move, and draw the balls. (This should make the form's code much simpler.)

· Make the form's code catch the Balls' HitWall events and play the appropriate sound.

NOTE

Please select the videos for Lesson 23 online at www.wrox.com/go/csharp24hourtrainer2evideos.