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.
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:
FileStream
as a parameter.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?
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 + "
" +
Street + "
" + 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() + "
" + MailStop;
}
Notice how the method calls the base class's version of GetAddress
to reuse that version of the method and avoid duplicated code.
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
.
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
and Employee
, 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.
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 Employee
s' 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 Person
s.
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 ListBox
es and ComboBox
es, 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's ToString
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.
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.
In this lesson, you:
virtual
keyword to the Shape
class's Draw
method.Draw
method in the Ellipse
class so it draws an ellipse instead of a rectangle.Paint
event handler to draw smooth shapes.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
.virtual
keyword to the Shape
class's Draw
method.
Shape
class's Draw
method so its declaration looks like this. (The virtual
keyword is highlighted in bold.)public virtual
void Draw(Graphics gr)
Draw
method in the Ellipse
class so it draws an ellipse instead of a rectangle.
// Draw the ellipse.
public override void Draw(Graphics gr)
{
using (Brush brush = new SolidBrush(Background))
{
gr.FillEllipse(brush, Bounds);
}
using (Pen pen = new Pen(Foreground, Thickness))
{
gr.DrawEllipse(pen, Bounds);
}
}
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.Paint
event handler to draw smooth shapes.
using
directive at the top of the form's code file:using System.Drawing.Drawing2D;
Paint
event handler:e.Graphics.SmoothingMode = SmoothingMode .AntiAlias;
Figure 25.2 shows the result for the objects I created.
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.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:
Rect
class analogous to the Ellipse
class.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.GetRandomParameters
method to generate random thickness, width, height, and position for a new shape.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 Ellipse
s and Circle
s as Shape
s.
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:
abstract
keyword before the class
keyword.Shape
class can still define drawing parameters (Bounds, Foreground, Background
, and Thickness
) and constructors.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:
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
.
Point
variables named StartPoint
and EndPoint
. Also create a class-level bool
variable named Drawing
and initialize it to false
.PictureBox
's MouseDown
event handler, save the mouse's location in StartPoint
and EndPoint
and set Drawing = true
.PictureBox
's MouseMove
event handler, if Drawing
is false
, return. Otherwise, save the mouse's current position in EndPoint
and refresh the PictureBox
.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.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.)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:
Tag
properties to store the line thickness values. (You'll need to parse those values when you need them.)ForeColor
properties to store colors.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.
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.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 an OverdraftAccount
. (Hint: Don't forget to make the Account
class's version of Debit
virtual
.)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?)