Fine-Tuning Classes - Classes - C# 24-Hour Trainer (2015)

C# 24-Hour Trainer (2015)

Section IV

Classes

Lesson 25

Fine-Tuning Classes

In Lesson 24 you learned how to build constructors and destructors, special methods that execute when an object is created or destroyed. In this lesson you learn about other special methods you can give a class. You learn how to overload and override class methods.

Overloading Methods

Lesson 24 mentioned that you can give a class any number of constructors as long as they have different parameter lists. For example, it's common to give a class a parameterless constructor that takes no parameters and one or more other constructors that take parameters.

Making multiple methods with the same name but different parameter lists is called overloading. C# uses the parameter list to decide which version to use when you invoke the method.

For example, suppose you're building a course assignment application and you have built Student, Course, and Instructor classes. You could give the Student class two versions of the Enroll method, one that takes as a parameter the name of a class the student is taking and a second that takes a Course object as a parameter.

You could give the Instructor class similar versions of the Teach method to make the instructor teach a class by name or Course object.

Finally, you could give the Course class different Report methods that:

· Display a report in a dialog if there are no parameters.

· Append a report to the end of a file if the method receives a FileStream as a parameter.

· Save the report into a new file if the method receives a string (the filename) as a parameter.

Making overloaded methods is so easy that there's little else to say. The only catch (and it's a tiny one) is that you need to be sure the parameter lists must differ in number, type, or arrangement. For example, consider the following two method declarations:

public void MakeReport(string fileToCreate)

{

...

}

public void MakeReport(string fileToAppend)

{

...

}

You might intend the first version to create a report in a file and the second to append a report to the end of a file. As far as C# is concerned, however, they both take a single string as a parameter. Even though the parameters have different names, C# wouldn't be able to tell which one to use under different circumstances. For example, which version should the statement MakeReport("MyReport.txt") use?

Overriding Methods

When one class inherits from another, you can add new properties, methods, and events to the new class to give it features that were not provided by the parent class.

Once in a while it's also useful to replace a method provided by the parent class with a new version. This is called overriding the parent's method.

Before you can override a method, you should mark the method in the parent class with the virtual keyword so it allows itself to be overridden. Next, add the keyword override to the derived class's version of the method to indicate that it overrides the parent class's version.

For example, suppose the Person class defines the usual assortment of properties: FirstName, LastName, Street, City, and so on. Suppose it also provides the following GetAddress method that returns the Person's name and address formatted for printing on an envelope:

// Return the Person's address.

public virtual string GetAddress()

{

return FirstName + " " + LastName + "\n" +

Street + "\n" + City + " " + State + " " + Zip;

}

Now suppose you derive the Employee class from Person. An Employee's address looks just like a Person's except it also includes MailStop. The MailStop property was added by the Employee class to indicate where to deliver mail within the company.

The following code shows how the Employee class can override the GetAddress method to return an Employee-style address:

// Return the Employee's address.

public override string GetAddress()

{

return base.GetAddress() + "\n" + MailStop;

}

Notice how the method calls the base class's version of GetAddress to reuse that version of the method and avoid duplicated code.

NOTE

IntelliSense can help you build overridden methods. For example, when you type public override and a space in the Employee class, IntelliSense lists the virtual methods that you might be trying to override. If you select one, IntelliSense fills in a default implementation for the new method. The following text shows the code IntelliSense generated for the GetAddress method:

public override string GetAddress()

{

return base.GetAddress();

}

The most miraculous thing about overriding a virtual method is that the object uses the method even if you invoke it from the base class. For example, suppose you have a Person variable pointing to an Employee object. Remember that an Employee is a kind of Person, so a Person variable can refer to an Employee as in the following code:

Employee bob = new Employee();

Person bobAsAPerson = bob;

Now if the code calls bobAsAPerson.GetAddress(), the result is the Employee version of GetAddress.

NOTE

You can think of the virtual keyword as making a slot in the base class for the method. When you override the method, the derived class fills this slot with a new version of the method. Now even if you call the method from the base class, it uses whatever is in the slot.

Overriding ToString

Overriding a class's ToString method is particularly useful. All classes inherit a ToString method from System.Object (the ultimate ancestor of all other classes), but the default implementation of ToString isn't always useful. For classes that you define, such as Person andEmployee, the default version of ToString simply returns the class's name. For example, in a program named ListPeople, the Employee class's ToString method would return “ListPeople.Employee.”

Although this correctly reports the object's class, it would be more useful if it returned something that contained information about the object's properties. In this example, it might be nice if it returned the Employee object's first and last names.

Fortunately the ToString method is virtual, so you can override it. The following code shows how you can override the ToString method to return an Employee's first and last name:

// Return first and last name.

public override string ToString()

{

return FirstName + " " + LastName;

}

This makes a lot more sense. Now your program can use an Employee object's ToString method to learn about the object.

Overriding ToString also has a nice side benefit for Windows Forms development. Certain controls and parts of Visual Studio use an object's ToString method to decide what to display. For example, the ListBox and ComboBox controls display lists of items. If those items are not simple strings, the controls use the items' ToString methods to generate output.

If the list is full of Employee objects and you've overridden the Employee class's ToString method, a ListBox or ComboBox can display the employees' names.

The ListPeople example program shown in Figure 25.1 (and available as part of this lesson's code download) demonstrates method overriding.

List People dialog box presenting four items (List_People.Student, List_People.Student, Cat Carter, and Dan Dental) and a Show Address button.

Figure 25.1

When it starts, the ListPeople program uses the following code to fill its ListBox with two Student objects and two Employee objects. Both of these classes inherit from Person.

private void Form1_Load(object sender, EventArgs e)

{

// Make some people.

peopleListBox.Items.Add(new Student("Ann", "Archer", "101 Ash Ave",

"Debugger", "NV", "72837"));

peopleListBox.Items.Add(new Student("Bob", "Best", "222 Beach Blvd",

"Debugger", "NV", "72837"));

peopleListBox.Items.Add(new Employee("Cat", "Carter", "300 Cedar Ct",

"Debugger", "NV", "72837", "MS-1"));

peopleListBox.Items.Add(new Employee("Dan", "Dental", "404 Date Dr",

"Debugger", "NV", "72837", "MS-2"));

}

The Employee class overrides its ToString method so you can see the Employees' names in Figure 25.1 instead of their class names. The Student class does not override its ToString method so Figure 25.1 shows class names for the Student objects.

If you select a person in this program and click the Show Address button, the program executes the following code:

// Display the selected Person's address.

private void showAddressButton_Click(object sender, EventArgs e)

{

Person person = peopleListBox.SelectedItem as Person;

MessageBox.Show(person.GetAddress());

}

This code converts the ListBox's selected item into a Person object. The item is actually either a Student or an Employee, but both of those inherit from Person (they are kinds of Person) so the program can treat them as Persons.

The program calls the Person's GetAddress method and displays the result. If the object was actually a Student, the result is a basic name and address. If the object was actually an Employee, the result is a name and address plus mailstop.

In addition to ListBoxes and ComboBoxes, some parts of Visual Studio use an object's ToString method, too. For example, if you stop an executing program and hover the mouse over an object in the debugger, a tooltip appears that displays the result of the object'sToString method. Similarly, if you type an object's name in the Immediate window and press Enter, the result is whatever is returned by the object's ToString method.

Try It

In this Try It, you improve the shape drawing program you built for Exercise 24-9 by overriding the Shape class's Draw method so Ellipse and Circle objects can draw themselves appropriately.

Lesson Requirements

In this lesson, you:

· Copy the program you wrote for Exercise 24-9 (or download the version that's available on the book's website).

· Add the virtual keyword to the Shape class's Draw method.

· Override the Draw method in the Ellipse class so it draws an ellipse instead of a rectangle.

· Modify the form's Paint event handler to draw smooth shapes.

NOTE

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

Hints

If gr is the Graphics object, you can use these techniques:

· gr.SmoothingMode = SmoothingMode.AntiAlias—Makes the object draw shapes smoothly. (SmoothingMode is defined in the System.Drawing.Drawing2D namespace.)

· gr.FillEllipse(brush, rect)—Fills an ellipse defined by the Rectangle rect with brush.

· gr.DrawEllipse(pen, rect)—Outlines an ellipse defined by the Rectangle rect with pen.

Step-by-Step

· Copy the program you wrote for Exercise 24-9 (or download the version that's available on the book's website).

1. This is straightforward.

· Add the virtual keyword to the Shape class's Draw method.

1. Change the Shape class's Draw method so its declaration looks like this. (The virtual keyword is highlighted in bold.)

public virtual void Draw(Graphics gr)

· Override the Draw method in the Ellipse class so it draws an ellipse instead of a rectangle.

1. Use code similar to the following:

2. // Draw the ellipse.

3. public override void Draw(Graphics gr)

4. {

5. using (Brush brush = new SolidBrush(Background))

6. {

7. gr.FillEllipse(brush, Bounds);

8. }

9. using (Pen pen = new Pen(Foreground, Thickness))

10. {

11. gr.DrawEllipse(pen, Bounds);

12. }

}

13.Note that you don't need to override the Circle class's Draw method. The Circle class inherits from Ellipse, so it will inherit the version shown here that's defined by the Ellipse class. The Circle class's constructors ensure that the Circle's width and height are the same, and that makes the ellipse-drawing code produce a circle.

· Modify the form's Paint event handler to draw smooth shapes.

1. Add the following using directive at the top of the form's code file:

using System.Drawing.Drawing2D;

2. Add the following statement at the beginning of the form's Paint event handler:

e.Graphics.SmoothingMode = SmoothingMode .AntiAlias;

Figure 25.2 shows the result for the objects I created.

Shapes dialog box presenting a variety of different shapes from circles to ellipses to rectangles filled with different colors. These shapes overlap per layer.

Figure 25.2

Exercises

1. [Graphics] Copy the program you built for the Try It and override the Shape class's Draw method to create a new version that takes a Pen and Brush as parameters and uses them to draw. Then make similar changes to the Ellipse and Circle classes. Test the new methods by modifying the form's code so it passes Pens.Red and Brushes.Pink into the objects' Draw methods.

2. [Graphics] Copy the program you built for Exercise 1 and add Rect and Square classes. (I'm calling the first of those classes Rect instead of Rectangle because .NET already has a Rectangle class so that name could cause confusion.) Modify the form's code to create a random Shape, Ellipse, Circle, Rect, and Square.

Hints:

· Make the Rect class analogous to the Ellipse class.

· Make the Square class somewhat analogous to the Circle class but give its constructor X and Y coordinates and a width instead of a center point and radius.

· Make a GetRandomParameters method to generate random thickness, width, height, and position for a new shape.

· Remove the code that makes all of the shapes pink so you can see the shapes' colors.

3. [Graphics, Advanced] The abstract keyword is somewhat similar to the virtual keyword. When you mark a method as abstract, you allow it to be overridden in derived classes. In fact, an abstract method has no code so you must override it before you can make an instance of the class.

Because you cannot make an instance of a class that contains an abstract method, you must also mark the class as abstract.

Why would you do this? Think about the program you wrote for the Try It. You probably don't really intend the program to make instances of the Shape class. It's really just there to be a base class so you can treat other objects such as Ellipses and Circles as Shapes.

Copy the program you wrote for Exercise 2 and make the Shape class's Draw methods abstract. Then modify the form's code so it doesn't try to make a Shape object. Hints:

· An abstract method cannot include any code. Just end it with a semicolon after the method's parameter list.

· Place the abstract keyword before the class keyword.

· An abstract class can contain non-abstract properties and methods and they are inherited as usual. In this example, the Shape class can still define drawing parameters (Bounds, Foreground, Background, and Thickness) and constructors.

4. [Graphics, Hard] Create a new program that displays a PictureBox with Cursor property set to Cross. Copy the shape classes you build for Exercise 25.3 into it. To copy a class from one project to another, you can create a class with the same name in the new project and then copy and paste its code into it. Alternatively you can:

· Copy the class's file into the new project's directory.

· Use the Project menu's Add Existing Item command to add the class to the project.

· Edit the class's code and change its namespace statement so it uses the same namespace as the rest of the project. (You can look at the top of the main form's code to see what the statement should look like.)

Next create a class-level List<Shape> named Shapes.

Write code to allow the user to select a rectangle on the PictureBox.

· Create two class-level Point variables named StartPoint and EndPoint. Also create a class-level bool variable named Drawing and initialize it to false.

· In the PictureBox's MouseDown event handler, save the mouse's location in StartPoint and EndPoint and set Drawing = true.

· In the PictureBox's MouseMove event handler, if Drawing is false, return. Otherwise, save the mouse's current position in EndPoint and refresh the PictureBox.

· In the PictureBox's MouseUp event handler, if Drawing is false, return. Otherwise, if StartPoint and EndPoint have different X and Y coordinates, use them to create a new Rect and add it to the Shapes list.

· In the PictureBox's Paint event handler, loop through the Shapes list and make the objects it contains draw themselves. Then if Drawing is true, draw a red dashed rectangle with corners at StartPoint and EndPoint. (Hints: The DrawRectangle method can't draw rectangles with negative widths or heights so you'll need to figure out where the upper-left corner of the newly selected rectangle is. The Math.Min and Math.Abs methods may help.)

5. [Graphics, Hard] Copy the program you built for Exercise 25.4 and add the toolbar holding four dropdown buttons shown in Figure 25.3.Image described by surrounding text.

Figure 25.3

The dropdown buttons represent the user's selected shape, line thickness, foreground color, and background color. When the user finishes selecting an area on the PictureBox, add the appropriate shape to the Shapes list. Hints:

· Use the properties of the menu items to store the selected values.

· Use the menu items' Tag properties to store the line thickness values. (You'll need to parse those values when you need them.)

· Use the menu items' ForeColor properties to store colors.

· Use the menu items' Text properties to store shape names.

Use code similar to the following when the user selects an item from the shapes dropdown button:

// Save this shape selection.

private void shapeMenuItem_Click(object sender, EventArgs e)

{

ToolStripMenuItem item = sender as ToolStripMenuItem;

shapeDropdownItem.Image = item.Image;

shapeDropdownItem.Tag = item.Text.Replace("&", "").ToLower();

}

This code is shared by all of the shape menu items. It converts the sender parameter into the ToolStripMenuItem that the user selected. It then copies that item's Image and Text (converted to lowercase and with any ampersands removed) into the dropdown button.

Use similar code for the other dropdown buttons' items. Copy the selected item's Image property and the appropriate value (Tag or ForeColor) into the dropdown button.

6. Copy the complex number program you built for Exercise 24-2 (or download the version that's available on the book's website). Override the class's ToString method so it returns the number in a form similar to “2 + 3i.” Overload the ComplexNumber class's AddTo,MultiplyBy, and SubtractFrom methods so you can pass them a single double parameter representing a real number with no imaginary part. Modify the form so you can test the new methods.

7. Copy the bank account program you built for Exercise 24-3 (or download the version that's available on the book's website). Derive a new OverdraftAccount class from the Account class. Give it a constructor that simply invokes the base class's constructor. Override the Debit method to allow the account to have a negative balance and charge a $50 fee if any debit leaves the account with a negative balance. Change the main program so the Account variable is still declared to be of type Account but initialize it as anOverdraftAccount. (Hint: Don't forget to make the Account class's version of Debit virtual.)

8. Copy Lesson 23's Turtle program. The Turtle class has a Move method that moves the turtle a specified distance in the object's current direction. Overload this method by making a second version that takes as parameters the X and Y coordinates where the turtle should move. Raise the OutOfBounds event if the point is not on the canvas. (Hint: Can you reuse code somehow between the two Move methods?)

NOTE

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