Chapter 2: Showing Some Class
In This Chapter
Introducing the C# class
Storing data in an object
Assigning and using object references
Examining classes that contain classes
Identifying static and instance class members
Using constants in C#
You can freely declare and use all the intrinsic data types — such as int
, double
, and bool
— to store the information necessary to make your program the best it can be. For some programs, these simple variables are enough. However, most programs need a way to bundle related data into a neat package.
As shown in Book I, C# provides arrays and other collections for gathering into one structure groups of like-typed variables, such as string
s or int
s. A hypothetical college, for example, might track its students by using an array. But a student is much more than just a name — how should this type of program represent a student?
Some programs need to bundle pieces of data that logically belong together but aren’t of the same type. A college enrollment application handles students, each with her own name, rank (grade-point average), and serial number. Logically, the student’s name may be a string
; the grade-point average, a double
; and the serial number, a long
. That type of program needs a way to bundle these three different types of variables into a single structure named Student
. Fortunately, C# provides a structure known as the class for accommodating groupings of unlike-typed variables.
Defining a Class and an Object
A class is a bundling of unlike data and functions that logically belong together into one tidy package. C# gives you the freedom to foul up your classes any way you want, but good classes are designed to represent concepts.
Computer science models the world via structures that represent concepts or things in the world, such as bank accounts, tic-tac-toe games, customers, game boards, documents, and products. Analysts say that “a class maps concepts from the problem into the program.” For example, your problem might be to build a traffic simulator that models traffic patterns for the purpose of building streets, intersections, and highways. (I really want you to build traffic simulators that can fix the intersection in front of my house.)
Any description of a problem concerning traffic would include the term vehicle in its solution. Vehicles have a top speed that must be figured into the equation. They also have a weight, and some of them are clunkers. In addition, vehicles stop and vehicles go. Thus, as a concept, vehicle is part of the problem domain.
A good C# traffic-simulator program would necessarily include the class Vehicle
, which describes the relevant properties of a vehicle. The C# Vehicle
class would have properties such as topSpeed
, weight
, and isClunker
.
Because the class is central to C# programming, the rest of Book II spelunks the ins and outs of classes in much more detail. This chapter gets you started.
Defining a class
An example of the class Vehicle
may appear this way:
public class Vehicle
{
public string model; // Name of the model
public string manufacturer; // Name of the manufacturer
public int numOfDoors; // The number of doors on the vehicle
public int numOfWheels; // You get the idea.
}
A class definition begins with the words public class
, followed by the name of the class — in this case, Vehicle
.
The class name is followed by a pair of open and closed braces. Within the braces, you have zero or more members. The members of a class are variables that make up the parts of the class. In this example, class Vehicle
starts with the member string model
, which contains the name of the model of the vehicle. If the vehicle were a car, its model name could be Trooper II. (Have you ever seen or heard of a Trooper I?) The second member of this Vehicle
class example is string manufacturer
. The other two properties are the number of doors and the number of wheels on the vehicle.
The public
modifier in front of the class name makes the class universally accessible throughout the program. Similarly, the public
modifier in front of the member names makes them accessible to everything else in the program. Other modifiers are possible. (Chapter 5 in this minibook covers the topic of accessibility in more detail and shows how you can hide some members.)
The class definition should describe the properties of the object that are salient to the problem at hand. That’s a little hard to do right now because you don’t know what the problem is, but you can see where I’m headed.
What’s the object?
Defining a Vehicle
design isn’t the same task as building a car. Someone has to cut some sheet metal and turn some bolts before anyone can drive an actual vehicle. A class object is declared in a similar (but not identical) fashion to declaring an intrinsic object such as an int
.
The following code segment creates a car of class Vehicle
:
Vehicle myCar;
myCar = new Vehicle();
The first line declares a variable myCar
of type Vehicle
, just like you can declare a somethingOrOther
of class int
. (Yes, a class is a type, and all C# objects are defined as classes.) The new Vehicle()
command creates a specific object of type Vehicle
and stores the location into the variable myCar
. The new
has nothing to do with the age of myCar
. (My car could qualify for an antique license plate if it weren’t so ugly.) The new
operator creates a new block of memory in which your program can store the properties of myCar
.
Compare the declaration of myCar
with that of an int
variable named num
:
int num;
num = 1;
The first line declares the variable num
, and the second line assigns an already created constant of type int
into the location of the variable num
.
Accessing the Members of an Object
Each object of class Vehicle
has its own set of members. The following expression stores the number 1 into the numberOfDoors
member of the object referenced by myCar
:
myCar.numberOfDoors = 1;
Similarly, the following code stores a reference to the string
s describing the model and manufacturer name of myCar
:
myCar.manufacturer = “BMW”; // Don’t get your hopes up.
myCar.model = “Isetta”; // The Urkel-mobile
The Isetta was a small car built during the 1950s with a single door that opened the entire front of the car. I leave Urkel to you and your favorite search engine.
An Object-Based Program Example
The simple VehicleDataOnly
program performs these tasks:
Define the class Vehicle
.
Create an object myCar
.
Assign properties to myCar
.
Retrieve those values from the object for display.
Here’s the code for the VehicleDataOnly
program:
// VehicleDataOnly -- Create a Vehicle object, populate its
// members from the keyboard, and then write it back out.
using System;
namespace VehicleDataOnly
{
public class Vehicle
{
public string model; // Name of the model
public string manufacturer; // Ditto
public int numOfDoors; // The number of doors on the vehicle
public int numOfWheels; // You get the idea.
}
public class Program
{
// This is where the program starts.
static void Main(string[] args)
{
// Prompt user to enter her name.
Console.WriteLine(“Enter the properties of your vehicle”);
// Create an instance of Vehicle.
Vehicle myCar = new Vehicle();
// Populate a data member via a temporary variable.
Console.Write(“Model name = “);
string s = Console.ReadLine();
myCar.model = s;
// Or you can populate the data member directly.
Console.Write(“Manufacturer name = “);
myCar.manufacturer = Console.ReadLine();
// Enter the remainder of the data.
// A temp is useful for reading ints.
Console.Write(“Number of doors = “);
s = Console.ReadLine();
myCar.numOfDoors = Convert.ToInt32(s);
Console.Write(“Number of wheels = “);
s = Console.ReadLine();
myCar.numOfWheels = Convert.ToInt32(s);
// Now display the results.
Console.WriteLine(“
Your vehicle is a “);
Console.WriteLine(myCar.manufacturer + “ “ + myCar.model);
Console.WriteLine(“with “ + myCar.numOfDoors + “ doors, “
+ “riding on “ + myCar.numOfWheels
+ “ wheels.”);
// Wait for user to acknowledge the results.
Console.WriteLine(“Press Enter to terminate...”);
Console.Read();
}
}
}
The program listing begins with a definition of the Vehicle
class.
The program creates an object myCar
of class Vehicle
and then populates each field by reading the appropriate data from the keyboard. (The input data isn’t — but should be — checked for legality.) The program then writes myCar
’s properties in a slightly different format.
The output from executing this program appears this way:
Enter the properties of your vehicle
Model name = Metropolitan
Manufacturer name = Nash
Number of doors = 2
Number of wheels = 4
Your vehicle is a
Nash Metropolitan
with 2 doors, riding on 4 wheels
Press Enter to terminate...
Discriminating between Objects
Detroit car manufacturers can track every car they make without getting the cars confused. Similarly, a program can create numerous objects of the same class, as shown in this example:
Vehicle car1 = new Vehicle();
car1.manufacturer = “Studebaker”;
car1.model = “Avanti”;
// The following has no effect on car1.
Vehicle car2 = new Vehicle();
car2.manufacturer = “Hudson”;
car2.model = “Hornet”;
Creating an object car2
and assigning it the manufacturer name Hudson
has no effect on the car1
object (with the manufacturer name Studebaker
).
In part, the ability to discriminate between objects is the real power of the class construct. The object associated with the Hudson Hornet can be created, manipulated, and dispensed with as a single entity, separate from other objects, including the Avanti. (Both are classic automobiles, especially the latter.)
Can You Give Me References?
The dot operator and the assignment operator are the only two operators defined on reference types:
// Create a null reference.
Vehicle yourCar;
// Assign the reference a value.
yourCar = new Vehicle();
// Use dot to access a member.
yourCar.manufacturer = “Rambler”;
// Create a new reference and point it to the same object.
Vehicle yourSpousalCar = yourCar;
The first line creates an object yourCar
without assigning it a value. A reference that hasn’t been initialized is said to point to the null object. Any attempt to use an uninitialized (null) reference generates an immediate error that terminates the program.
The second statement creates a new Vehicle
object and assigns it to yourCar
. The last statement in this code snippet assigns the reference yourSpousalCar
to the reference yourCar
. This action causes yourSpousalCar
to refer to the same object as yourCar
. This relationship is shown in Figure 2-1.
Figure 2-1: Two references to the same object.
The following two calls have the same effect:
// Build your car.
Vehicle yourCar = new Vehicle();
yourCar.model = “Kaiser”;
// It also belongs to your spouse.
Vehicle yourSpousalCar = yourCar;
// Changing one changes the other.
yourSpousalCar.model = “Henry J”;
Console.WriteLine(“Your car is a “ + yourCar.model);
Executing this program would output Henry J
and not Kaiser
. Notice that yourSpousalCar
doesn’t point to yourCar
; rather, both yourCar
and yourSpousalCar
refer to the same vehicle.
In addition, the reference yourSpousalCar
would still be valid, even if the variable yourCar
were somehow “lost” (if it went out of scope, for example), as shown in this chunk of code:
// Build your car.
Vehicle yourCar = new Vehicle();
yourCar.model = “Kaiser”;
// It also belongs to your spouse.
Vehicle yourSpousalCar = yourCar;
// When your spouse takes your car away . . .
yourCar = null; // yourCar now references the “null object.”
// . . .yourSpousalCar still references the same vehicle
Console.WriteLine(“your car was a “ + yourSpousalCar.model);
Executing this program generates the output Your car was a Kaiser
, even though the reference yourCar
is no longer valid.
At that point — well, at some unpredictable later point, anyway — the C# garbage collector steps in and returns the space formerly used by that particular Vehicle
object to the pool of space available for allocating more Vehicles
(or Students
, for that matter). (I say a little more about garbage collection in a sidebar at the end of Chapter 6 in this minibook.)
Classes That Contain Classes Are theHappiest Classes in the World
The members of a class can themselves be references to other classes. For example, vehicles have motors, which have power and efficiency factors, including displacement. You could throw these factors directly into the class this way:
public class Vehicle
{
public string model; // Name of the model
public string manufacturer; // Ditto
public int numOfDoors; // The number of doors on the vehicle
public int numOfWheels; // You get the idea.
// New stuff:
public int power; // Power of the motor [horsepower]
public double displacement; // Engine displacement [liter]
}
However, power and engine displacement aren’t properties of the car. For example, your friend’s Jeep might be supplied with two different motor options that have drastically different levels of horsepower. The 2.4-liter Jeep is a snail, and the same car outfitted with the 4.0-liter engine is quite peppy.
The motor is a concept of its own and deserves its own class:
public class Motor
{
public int power; // Power [horsepower]
public double displacement; // Engine displacement [liter]
}
You can combine this class into the Vehicle
(see boldfaced text):
public class Vehicle
{
public string model; // Name of the model
public string manufacturer; // Ditto
public int numOfDoors; // The number of doors on the vehicle
public int numOfWheels; // You get the idea.
public Motor motor;
}
Creating myCar
now appears this way:
// First create a Motor.
Motor largerMotor = new Motor();
largerMotor.power = 230;
largerMotor.displacement = 4.0;
// Now create the car.
Vehicle friendsCar = new Vehicle();
friendsCar.model = “Cherokee Sport”;
friendsCar.manfacturer = “Jeep”;
friendsCar.numOfDoors = 2;
friendsCar.numOfWheels = 4;
// Attach the motor to the car.
friendsCar
.motor = largerMotor;
From Vehicle
, you can access the motor displacement in two stages. You can take one step at a time, as this bit of code shows:
Motor m = friendsCar.motor;
Console.WriteLine(“The motor displacement is “ + m.displacement);
Or, you can access it directly, as shown here:
Console.Writeline(“The motor displacement is “ + friendsCar.motor.displacement);
Either way, you can access the displacement
only through the Motor
.
Generating Static in Class Members
Most data members of a class are specific to their containing object, not to any other objects. Consider the Car
class:
public class Car
{
public string licensePlate; // The license plate ID
}
Because the license plate ID is an object property, it describes each object of class Car
uniquely. For example, thank goodness that my car has a license plate that’s different from yours; otherwise, you may not make it out of your driveway, as shown in this bit of code:
Car myCar = new Car();
myCar.licensePlate = “XYZ123”;
Car yourCar = new Car();
yourCar.licensePlate = “ABC789”;
However, some properties exist that all cars share. For example, the number of cars built is a property of the class Car
but not of any single object. These class properties are flagged in C# with the keyword static
:
public class Car
{
public
static
int numberOfCars; // The number of cars built
public string licensePlate; // The license plate ID
}
// Create a new object of class Car.
Car newCar = new Car();
newCar.licensePlate = “ABC123”;
// Now increment the count of cars to reflect the new one.
Car.numberOfCars++;
The object member newCar.licensePlate
is accessed through the object newCar
, and the class (static) member Car.numberOfCars
is accessed through the class Car
. All Car
s share the same numberOfCars
member, so each car contains exactly the same value as all other cars.
Defining const and readonly Data Members
One special type of static member is the const
data member, which represents a constant. You must establish the value of a const
variable in the declaration, and you cannot change it anywhere within the program, as shown here:
class Program
{
// Number of days in the year (including leap day)
public const int daysInYear = 366; // Must have initializer.
public static void Main(string[] args)
{
// This is an array, covered later in this chapter.
int[] maxTemperatures = new int[
daysInYear
];
for(int index = 0; index <
daysInYear
; index++)
{
// . . .accumulate the maximum temperature for each
// day of the year . . .
}
}
}
You can use the constant daysInYear
in place of the value 366
anywhere within your program. The const
variable is useful because it can replace a mysterious number such as 366
with the descriptive name daysInYear
to enhance the readability of your program.
public
readonly
int daysInYear = 366; // This could also be static.
As with const
, after you assign the initial value, it can’t be changed. Although the reasons are too technical for this book, the readonly
approach to declaring constants is usually preferable to const
.
You can use const
with class data members like those you might have seen in this chapter and inside class methods. But readonly
isn’t allowed in a method. Chapter 3 of this minibook dives into methods.
An alternative convention also exists for naming constants. Rather than name them like variables (as in daysInYear
), many programmers prefer to use uppercase letters separated by underscores, as in DAYS_IN_YEAR
. This convention separates constants clearly from ordinary read-write variables.