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.
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 Button
s 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.
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 Turn
to 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, Pen
s, 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:
Turtle
move is right in the same object as the data that determines the Turtle
's position and direction.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.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.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:
Employee
class might define FirstName
, LastName
, and EmployeeId
properties.Employee
class might provide a CalculateBonus
method that calculates the employee's end-of-year bonus based on performance during the year.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.
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.
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.
The following code creates a simple property named FirstName
of type string
:
public string FirstName { get; set; }
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.
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.
In this lesson, you:
Person
class.Street
, City
, State
, and Zip
.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.Person
class.
Person
.Street
, City
, State
, and Zip
.
// Auto-implemented properties.
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
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.
FirstName
property. The code for the LastName
property is similar.
// FirstName property.
private string firstName = "";// Backing field.
public string FirstName
{
get
{
return firstName;
}
set
{
if (value == null)
throw new ArgumentOutOfRangeException("FirstName",
"Person.FirstName cannot be null.");
if (value.Length < 1)
throw new ArgumentOutOfRangeException("FirstName",
"Person.FirstName cannot be blank.");
}
}
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 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.
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.
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 an event 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's Turtle_OutOfBounds
event handler executes.
// Register to receive the OutOfBounds event.
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));
}
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.
In this lesson, you:
BankAccount
class. Give it a Balance
property.Debit
and Credit
methods to add and remove money from the account.AccountOverdrawnArgs
class to pass to event handlers.OverdrawnEventHandler
delegate type.Overdrawn
event itself.Debit
method raise the event when necessary.Overdrawn
event so it can display a message box.Balance
property so you can make it auto-implemented.BankAccount
class to manipulate.BankAccount
class. Give it a Balance
property.
// The account balance.
public decimal Balance { get; set; }
Debit
and Credit
methods to add and remove money from the account.
Debit
method later to raise the Overdrawn
event.
// Add money to the account.
public void Credit(decimal amount)
{
Balance += amount;
}
// Remove money from the account.
public void Debit(decimal amount)
{
Balance -= amount;
}
AccountOverdrawnArgs
class to pass to event handlers.
// Define the OverdrawnEventArgs type.
public class OverdrawnEventArgs
{
public decimal currentBalance, invalidBalance;
}
OverdrawnEventHandler
delegate type.
// Define the OverdrawnEventHandler delegate type.
public delegate void OverdrawnEventHandler(
object sender, OverdrawnEventArgs args);
Overdrawn
event itself.
// Declare the Overdrawn event.
public event OverdrawnEventHandler Overdrawn;
Debit
method raise the event when necessary.
// Remove money from the account.
public void Debit(decimal amount)
{
// See if there is enough money.
if (Balance < amount)
{
// Not enough money. Raise the Overdrawn event.
if (Overdrawn != null)
{
OverdrawnEventArgs args = new OverdrawnEventArgs();
args.currentBalance = Balance;
args.invalidBalance = Balance - amount;
Overdrawn(this, args);
}
}
else
{
// There's enough money.
Balance -= amount;
}
}
// Add money to the account.
private void creditButton_Click(object sender, EventArgs e)
{
// Add the money.
decimal amount = decimal.Parse(amountTextBox.Text);
MyAccount.Credit(amount);
// Display the current balance.
balanceTextBox.Text = MyAccount.Balance.ToString("C");
}
// Remove money from the account.
private void debitButton_Click(object sender, EventArgs e)
{
// Remove the money.
decimal amount = decimal.Parse(amountTextBox.Text);
MyAccount.Debit(amount);
// Display the current balance.
balanceTextBox.Text = MyAccount.Balance.ToString("C");
}
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 Manager
s are also Employee
s) 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 the Employee
class provides, Manager
adds new DepartmentName
and DirectReports
properties:
class Manager : Employee
{
public string DepartmentName { get; set; }
public List<Employee> DirectReports = new List<Employee>();
}
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, an Employee
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.
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.
In this lesson, you:
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:
Employee
class from Person
. Add the properties EmployeeId
and MailStop
.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 Employee
s separated by newlines.Employee
s named Alice and Bob, a Manager
named Cindy who has Alice and Bob in her department, and a Person
named Dan.ShowAddress
method that takes a Person
as a parameter and displays the Person
's address.ShowAddress
for each of the people, passing the method the appropriate object.ShowAddress
method should take a Person
parameter even though some of the objects it will be passed are Employee
s or Manager
s.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:
Make a new Person
class with code similar to the following:
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
// Display the person's address.
// A real application might print this on an envelope.
public string GetAddress()
{
return FirstName + " " + LastName +
"
" + Street + "
" + City +
" " + State + " " + Zip;
}
}
Employee
class from Person
. Add the properties EmployeeId
and MailStop
.
Make the Employee
class similar to the following:
class Employee : Person
{
public int EmployeeId { get; set; }
public string MailStop { get; set; }
}
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 Employee
s separated by newlines.
Make the Manager
class similar to the following:
class Manager : Employee
{
public string DepartmentName { get; set; }
public List<Employee> DirectReports = new List<Employee>();
// Return a list of this manager's direct reports.
public string GetDirectReportsList()
{
string result = "";
foreach (Employee emp in DirectReports)
{
result += emp.FirstName + " " + emp.LastName + "
";
}
return result;
}
}
Employee
s named Alice and Bob, a Manager
named Cindy who has Alice and Bob in her department, and a Person
named Dan.
Because the program's buttons need to access the objects, these objects should be stored in class-level fields as in the following code:
// Define some people of various types.
private Person Dan;
private Employee Alice, Bob;
private Manager Cindy;
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:
// Make an Employee named Alice.
Alice = new Employee();
Alice.FirstName = "Alice";
Alice.LastName = "Archer";
Alice.Street = "100 Ash Ave";
Alice.City = "Bugsville";
Alice.State = "CO";
Alice.Zip = "82010";
Alice.EmployeeId = 1001;
Alice.MailStop = "A-1";
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:
Cindy.DirectReports.Add(Alice);
Cindy.DirectReports.Add(Bob);
ShowAddress
method that takes a Person
as a parameter and displays the Person
's address.
Use code similar to the following:
// Display this Person's address.
private void ShowAddress(Person person)
{
MessageBox.Show(person.GetAddress());
}
ShowAddress
for each of the people, passing the method the appropriate object.
Create the buttons' Click
event handlers. The following code shows the event handler that displays Cindy's address:
private void cindyAddressButton_Click(object sender, EventArgs e)
{
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
.
This method simply calls the Cindy
object's GetDirectReportsList
method and displays the result:
// Display Cindy's direct reports.
private void cindyReportsButton_Click(object sender, EventArgs e)
{
MessageBox.Show(Cindy.GetDirectReportsList());
}
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.
Hints:
return
statement if a method returns a value.)string
s 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.Person
and Student
classes. Give the Student
class (directly or via inheritance) typical name and address properties, plus a list to hold the courses (string
s) 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.)
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.Enroll
method so it raises an Overenrolled
event instead of throwing an exception if the student tries to enroll in more than six courses.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
.
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:
using System.Drawing
to the file that defines the Ball
class.Ball
class the fields (or properties) X
, Y
, Vx
, Vy
, Width
, Height
, and Brush
. Also give it a new ClientSize
property of type Size
.Ball
class an Initialize
method that randomizes the Ball
's properties. Hints:
ClientSize
into the Initialize
method. Make the method save it in the Ball
's ClientSize
field.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.)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 Ball
s 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 Ball
s with the same positions, velocities, and colors. Using the static
keyword makes all of the Ball
s 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.)Ball
class a Move
method that updates the Ball
's position. If the Ball
hits a wall, raise a HitWall
event.Ball
class a Draw
method that takes a Graphics
object as a parameter and draws the ball on it.Ball
methods to initialize, move, and draw the balls. (This should make the form's code much simpler.)Ball
s' HitWall
events and play the appropriate sound.