CHAPTER 6
Introducing Classes and Objects

This chapter introduces the class. The class is the foundation of C# because it defines the nature of an object. Furthermore, the class forms the basis for object-oriented programming. Within a class are defined both code and data. Because classes and objects are fundamental to C#, they constitute a large topic, which spans several chapters. This chapter begins the discussion by covering their main features.

Class Fundamentals

We have been using classes since the start of this book. Of course, only extremely simple classes have been used, and we have not taken advantage of the majority of their features. Classes are substantially more powerful than the limited ones presented so far.

Let’s begin by reviewing the basics. A class is a template that defines the form of an object. It specifies both the data and the code that will operate on that data. C# uses a class specification to construct objects. Objects are instances of a class. Thus, a class is essentially a set of plans that specify how to build an object. It is important to be clear on one issue: A class is a logical abstraction. It is not until an object of that class has been created that a physical representation of that class exists in memory.

The General Form of a Class

When you define a class, you declare the data that it contains and the code that operates on it. While very simple classes might contain only code or only data, most real-world classes contain both.

In general terms, data is contained in data members defined by the class, and code is contained in function members. It is important to state at the outset that C# defines several specific flavors of data and function members. For example, data members (also called fields) include instance variables and static variables. Function members include methods, constructors, destructors, indexers, events, operators, and properties. For now, we will limit our discussion of the class to its essential elements: instance variables and methods. Later in this chapter constructors and destructors are discussed. The other types of members are described in later chapters.

A class is created by use of the keyword class. Here is the general form of a simple class definition that contains only instance variables and methods:

class classname {

// declare instance variables

access type var1;

access type var2;

// ...

access type varN;

// declare methods

access ret-type method1(parameters){

// body of method

}

access ret-type method2(parameters){

// body of method

}

// ...

access ret-type methodN(parameters){

// body of method

}

}

Notice that each variable and method declaration is preceded with access. Here, access is an access specifier, such as public, which specifies how the member can be accessed. As mentioned in Chapter 2, class members can be private to a class or more accessible. The access specifier determines what type of access is allowed. The access specifier is optional, and if absent, then the member is private to the class. Members with private access can be used only by other members of their class. For the examples in this chapter, all members (except for the Main( ) method) will be specified as public, which means that they can be used by all other code—even code defined outside the class. We will return to the topic of access specifiers in Chapter 8.


NOTE In addition to an access specifier, the declaration of a class member can also contain one or more type modifiers. These modifiers are discussed later in this book.

Although there is no syntactic rule that enforces it, a well-designed class should define one and only one logical entity. For example, a class that stores names and telephone numbers will not normally also store information about the stock market, average rainfall, sunspot cycles, or other unrelated information. The point here is that a well-designed class groups logically connected information. Putting unrelated information into the same class will quickly destructure your code.

Up to this point, the classes that we have been using have had only one method: Main( ). However, notice that the general form of a class does not specify a Main( ) method. A Main( ) method is required only if that class is the starting point for your program.

Define a Class

To illustrate classes, we will be evolving a class that encapsulates information about buildings, such as houses, stores, offices, and so on. This class is called Building, and it will store three items of information about a building: the number of floors, the total area, and the number of occupants.

The first version of Building is shown here. It defines three instance variables: Floors, Area, and Occupants. Notice that Building does not contain any methods. Thus, it is currently a data-only class. (Subsequent sections will add methods to it.)

class Building {
  public int Floors;    // number of floors
  public int Area;      // total square footage of building
  public int Occupants; // number of occupants
}

The instance variables defined by Building illustrate the way that instance variables are declared in general. The general form for declaring an instance variable is shown here:

access type var-name;

Here, access specifies the access; type specifies the type of variable; and var-name is the variable’s name. Thus, aside from the access specifier, you declare an instance variable in the same way that you declare local variables. For Building, the variables are preceded by the public access modifier. As explained, this allows them to be accessed by code outside of Building.

A class definition creates a new data type. In this case, the new data type is called Building. You will use this name to declare objects of type Building. Remember that a class declaration is only a type description; it does not create an actual object. Thus, the preceding code does not cause any objects of type Building to come into existence.

To actually create a Building object, you will use a statement like the following:

Building house = new Building(); // create an object of type building

After this statement executes, house will be an instance of Building. Thus, it will have “physical” reality. For the moment, don’t worry about the details of this statement.

Each time you create an instance of a class, you are creating an object that contains its own copy of each instance variable defined by the class. Thus, every Building object will contain its own copies of the instance variables Floors, Area, and Occupants. To access these variables, you will use the member access operator, which is a period. It is commonly referred to as the dot operator. The dot operator links the name of an object with the name of a member. The general form of the dot operator is shown here:

object.member

Thus, the object is specified on the left, and the member is put on the right. For example, to assign the Floors variable of house the value 2, use the following statement:

house.Floors = 2;

In general, you can use the dot operator to access both instance variables and methods.

Here is a complete program that uses the Building class:

// A program that uses the Building class.

using System;
class Building {
  public int Floors;    // number of floors
  public int Area;      // total square footage of building
  public int Occupants; // number of occupants
}

// This class declares an object of type Building.
class BuildingDemo {
  static void Main() {
    Building house = new Building(); // create a Building object
    int areaPP; // area per person

    // Assign values to fields in house.
    house.Occupants = 4;
    house.Area = 2500;
    house.Floors = 2;
 
    // Compute the area per person.
    areaPP = house.Area / house.Occupants;

     Console.WriteLine("house has:\n  " +
                      house.Floors + " floors\n  " +
                      house.Occupants + " occupants\n  " +
                      house.Area + " total area\n  " +
                      areaPP + " area per person");
  }
}

This program consists of two classes: Building and BuildingDemo. Inside BuildingDemo, the Main( ) method creates an instance of Building called house. Then the code within Main( ) accesses the instance variables associated with house, assigning them values and using those values. It is important to understand that Building and BuildingDemo are two separate classes. The only relationship they have to each other is that one class creates an instance of the other. Although they are separate classes, code inside BuildingDemo can access the members of Building because they are declared public. If they had not been given the public access specifier, their access would have been limited to the Building class, and BuildingDemo would not have been able to use them.

Assume that you call the preceding file UseBuilding.cs. Compiling this program creates a file called UseBuilding.exe. Both the Building and BuildingDemo classes are automatically part of the executable file. The program displays the following output:

house has:
  2 floors
  4 occupants
  2500 total area
  625 area per person

It is not necessary for the Building and the BuildingDemo classes to actually be in the same source file. You could put each class in its own file, called Building.cs and BuildingDemo.cs, for example. Just tell the C# compiler to compile both files and link them together. For example, you could use this command line to compile the program if you split it into two pieces as just described:

csc Building.cs BuildingDemo.cs

If you are using the Visual Studio IDE, you will need to add both files to your project and then build.

Before moving on, let’s review a fundamental principle: Each object has its own copies of the instance variables defined by its class. Thus, the contents of the variables in one object can differ from the contents of the variables in another. There is no connection between the two objects except for the fact that they are both objects of the same type. For example, if you have two Building objects, each has its own copy of Floors, Area, and Occupants, and the contents of these can (and often will) differ between the two objects. The following program demonstrates this fact:

// This program creates two Building objects.

using System;

class Building {
  public int Floors;    // number of floors
  public int Area;      // total square footage of building
  public int Occupants; // number of occupants
}

// This class declares two objects of type Building.
class BuildingDemo {
  static void Main() {
    Building house = new Building();
    Building office = new Building();

    int areaPP; // area per person

    // Assign values to fields in house.
    house.Occupants = 4;
    house.Area = 2500;
    house.Floors = 2;
 
    // Assign values to fields in office.
    office.Occupants = 25;
    office.Area = 4200;
    office.Floors = 3;
 
    // Compute the area per person in house.
    areaPP = house.Area / house.Occupants;

     Console.WriteLine("house has:\n  " +
                      house.Floors + " floors\n  " +
                      house.Occupants + " occupants\n  " +
                      house.Area + " total area\n  " +
                      areaPP + " area per person");
    Console.WriteLine();

    // Compute the area per person in office.
    areaPP = office.Area / office.Occupants;

    Console.WriteLine("office has:\n  " +
                      office.Floors + " floors\n  " +
                      office.Occupants + " occupants\n  " +
                      office.Area + " total area\n  " +
                      areaPP + " area per person");
  }
}

The output produced by this program is shown here:

house has:
  2 floors
  4 occupants
  2500 total area
  625 area per person

office has:
  3 floors
  25 occupants
  4200 total area
  168 area per person

As you can see, house’s data is completely separate from the data contained in office. Figure 6-1 depicts this situation.

How Objects Are Created

In the preceding programs, the following line was used to declare an object of type Building:

Building house = new Building();

This declaration performs three functions. First, it declares a variable called house of the class type Building. This variable is not, itself, an object. Instead, it is simply a variable that can refer to an object. Second, the declaration creates an actual, physical copy of the object. This is done by using the new operator. Finally, it assigns to house a reference to that object. Thus, after the line executes, house refers to an object of type Building.

FIGURE 6-1 One object’s instance variables are separate from another’s.

Image

The new operator dynamically allocates (that is, allocates at runtime) memory for an object and returns a reference to it. This reference is then stored in a variable. Thus, in C#, all class objects must be dynamically allocated.

As you might expect, it is possible to separate the declaration of house from the creation of the object to which it will refer, as shown here:

Building house; // declare reference to object
house = new Building(); // allocate a Building object

The first line declares house as a reference to an object of type Building. Thus, house is a variable that can refer to an object, but it is not an object, itself. The next line creates a new Building object and assigns a reference to it to house. Now, house is linked with an object.

The fact that class objects are accessed through a reference explains why classes are called reference types. The key difference between value types and reference types is what a variable of each type means. For a value type variable, the variable, itself, contains the value. For example, given

int x;
x = 10;

x contains the value 10 because x is a variable of type int, which is a value type. However, in the case of

Building house = new Building();

house does not, itself, contain the object. Instead, it contains a reference to the object.

Reference Variables and Assignment

In an assignment operation, reference variables act differently than do variables of a value type, such as int. When you assign one value type variable to another, the situation is straightforward. The variable on the left receives a copy of the value of the variable on the right. When you assign one object reference variable to another, the situation is a bit more complicated because the assignment causes the reference variable on the left to refer to the same object to which the reference variable on the right refers. The object, itself, is not copied. The effect of this difference can cause some counterintuitive results. For example, consider the following fragment:

Building house1 = new Building();
Building house2 = house1;

At first glance, it is easy to think that house1 and house2 refer to separate and distinct objects, but this is not the case. Instead, house1 and house2 will both refer to the same object. The assignment of house1 to house2 simply makes house2 refer to the same object that house1 does. Thus, the object can be acted upon by either house1 or house2. For example, after the assignment

house1.Area = 2600;

executes, both of these WriteLine( ) statements

Console.WriteLine(house1.Area);
Console.WriteLine(house2.Area);

display the same value: 2600.

Although house1 and house2 both refer to the same object, they are not linked in any other way. For example, a subsequent assignment to house2 simply changes what object house2 refers to. For example:

Building house1 = new Building();
Building house2 = house1;
Building house3 = new Building();

house2 = house3; // now house2 and house3 refer to the same object.

After this sequence executes, house2 refers to the same object as house3. The object referred to by house1 is unchanged.

Methods

As explained, instance variables and methods are two of the primary constituents of classes. So far, the Building class contains data, but no methods. Although data-only classes are perfectly valid, most classes will have methods. Methods are subroutines that manipulate the data defined by the class and, in many cases, provide access to that data. Typically, other parts of your program will interact with a class through its methods.

A method contains one or more statements. In well-written C# code, each method performs only one task. Each method has a name, and it is this name that is used to call the method. In general, you can name a method using any valid identifier that you please. However, remember that Main( ) is reserved for the method that begins execution of your program. Also, don’t use C#’s keywords for method names.

When denoting methods in text, this book has used and will continue to use a convention that has become common when writing about C#. A method will have parentheses after its name. For example, if a method’s name is GetVal, then it will be written GetVal( ) when its name is used in a sentence. This notation will help you distinguish variable names from method names in this book.

The general form of a method is shown here:

access ret-type name (parameter-list) {

// body of method

}

Here, access is an access modifier that governs what other parts of your program can call the method. As explained earlier, the access modifier is optional. If not present, then the method is private to the class in which it is declared. For now, we will declare methods as public so that they can be called by any other code in the program. The ret-type specifies the type of data returned by the method. This can be any valid type, including class types that you create. If the method does not return a value, its return type must be void. The name of the method is specified by name. This can be any legal identifier other than those that would cause conflicts within the current declaration space. The parameter-list is a sequence of type and identifier pairs separated by commas. Parameters are variables that receive the value of the arguments passed to the method when it is called. If the method has no parameters, then the parameter list will be empty.

Add a Method to the Building Class

As just explained, the methods of a class typically manipulate and provide access to the data of the class. With this in mind, recall that Main( ) in the preceding examples computed the area-per-person by dividing the total area by the number of occupants. Although technically correct, this is not the best way to handle this computation. The calculation of area-per-person is something that is best handled by the Building class, itself. The reason for this conclusion is easy to understand: The area-per-person of a building is dependent upon the values in the Area and Occupants fields, which are encapsulated by Building. Thus, it is possible for the Building class to perform this calculation on its own. Furthermore, by adding this calculation to Building, you prevent each program that uses Building from having to perform this calculation manually. This prevents the unnecessary duplication of code. Finally, by adding a method to Building that computes the area-per-person, you are enhancing its object-oriented structure by encapsulating the quantities that relate directly to a building inside Building.

To add a method to Building, specify it within Building’s declaration. For example, the following version of Building contains a method called AreaPerPerson( ) that displays the area-per-person for a building:

// Add a method to Building.

using System;

class Building {
  public int Floors;    // number of floors
  public int Area;      // total square footage of building
  public int Occupants; // number of occupants

  // Display the area per person.
  public void AreaPerPerson() {
    Console.WriteLine("  " + Area / Occupants + " area per person");
  }
}

// Use the AreaPerPerson() method.
class BuildingDemo {
  static void Main() {
    Building house = new Building();
    Building office = new Building();

    // Assign values to fields in house.
    house.Occupants = 4;
    house.Area = 2500;
    house.Floors = 2;

    // Assign values to fields in office.
    office.Occupants = 25;
    office.Area = 4200;
    office.Floors = 3;

    Console.WriteLine("house has:\n  " +
                      house.Floors + " floors\n  " +
                      house.Occupants + " occupants\n  " +
                      house.Area + " total area");
    house.AreaPerPerson();

    Console.WriteLine();

    Console.WriteLine("office has:\n  " +
                      office.Floors + " floors\n  " +
                      office.Occupants + " occupants\n  " +
                      office.Area + " total area");
    office.AreaPerPerson();
  }
}

This program generates the following output, which is the same as before:

house has:
  2 floors
  4 occupants
  2500 total area
  625 area per person

office has:
  3 floors
  25 occupants
  4200 total area
  168 area per person

Let’s look at the key elements of this program, beginning with the AreaPerPerson( ) method, itself. The first line of AreaPerPerson( ) is

public void AreaPerPerson() {

This line declares a method called AreaPerPerson( ) that has no parameters. It is specified as public, so it can be used by all other parts of the program. Its return type is void. Thus, AreaPerPerson( ) does not return a value to the caller. The line ends with the opening curly brace of the method body.

The body of AreaPerPerson( ) consists solely of this statement:

Console.WriteLine("  " + Area / Occupants + " area per person");

This statement displays the area-per-person of a building by dividing Area by Occupants. Since each object of type Building has its own copy of Area and Occupants, when AreaPerPerson( ) is called, the computation uses the calling object’s copies of those variables.

The AreaPerPerson( ) method ends when its closing curly brace is encountered. This causes program control to transfer back to the caller.

Next, look closely at this line of code from inside Main( ):

house.AreaPerPerson();

This statement invokes the AreaPerPerson( ) method on house. That is, it calls AreaPerPerson( ) relative to the object referred to by house, by use of the dot operator. When a method is called, program control is transferred to the method. When the method terminates, control is transferred back to the caller, and execution resumes with the line of code following the call.

In this case, the call to house.AreaPerPerson( ) displays the area-per-person of the building defined by house. In similar fashion, the call to office.AreaPerPerson( ) displays the area-per-person of the building defined by office. Each time AreaPerPerson( ) is invoked, it displays the area-per-person for the specified object.

There is something very important to notice inside the AreaPerPerson( ) method: The instance variables Area and Occupants are referred to directly, without use of the dot operator. When a method uses an instance variable that is defined by its class, it does so directly, without explicit reference to an object and without use of the dot operator. This is easy to understand if you think about it. A method is always invoked relative to some object of its class. Once this invocation has occurred, the object is known. Thus, within a method, there is no need to specify the object a second time. This means that Area and Occupants inside AreaPerPerson( ) implicitly refer to the copies of those variables found in the object that invokes AreaPerPerson( ).


NOTE As a point of interest, in the AreaPerPerson( ) method, Occupants must not equal zero (which it won’t for all of the examples in this chapter). If Occupants were zero, then a division-by-zero error would occur. In Chapter 13, you will learn about exceptions, which are C#’s approach to handling errors, and see how to watch for errors that can occur at runtime.

Return from a Method

In general, there are two conditions that cause a method to return. The first, as the AreaPerPerson( ) method in the preceding example shows, is when the method’s closing curly brace is encountered. The second is when a return statement is executed. There are two forms of return: one for use in void methods (those that do not return a value) and one for returning values. The first form is examined here. The next section explains how to return values.

In a void method, you can cause the immediate termination of a method by using this form of return:

return ;

When this statement executes, program control returns to the caller, skipping any remaining code in the method. For example, consider this method:

public void MyMeth() {
  int i;

  for(i=0; i<10; i++) {
    if(i == 5) return; // stop at 5
    Console.WriteLine();
  }
}

Here, the for loop will only run from 0 to 5, because once i equals 5, the method returns.

It is permissible to have multiple return statements in a method, especially when there are two or more routes out of it. For example,

public void MyMeth() {
  // ...
  if(done) return;
  // ...
  if(error) return;
}

Here, the method returns if it is done or if an error occurs. Be careful, however. Having too many exit points in a method can destructure your code, so avoid using them casually.

To review: A void method can return in one of two ways—its closing curly brace is reached, or a return statement is executed.

Return a Value

Although methods with a return type of void are not rare, most methods will return a value. In fact, the ability to return a value is one of a method’s most useful features. You have already seen an example of a return value when we used the Math.Sqrt( ) function in Chapter 3 to obtain a square root.

Return values are used for a variety of purposes in programming. In some cases, such as with Math.Sqrt( ), the return value contains the outcome of some calculation. In other cases, the return value may simply indicate success or failure. In still others, it may contain a status code. Whatever the purpose, using method return values is an integral part of C# programming.

Methods return a value to the calling routine using this form of return:

return value;

Here, value is the value returned.

You can use a return value to improve the implementation of AreaPerPerson( ). Instead of displaying the area-per-person, a better approach is to have AreaPerPerson( ) return this value. Among the advantages to this approach is that you can use the value for other calculations. The following example modifies AreaPerPerson( ) to return the area-per-person rather than displaying it:

// Return a value from AreaPerPerson().

using System;

class Building {
  public int Floors;    // number of floors
  public int Area;      // total square footage of building
  public int Occupants; // number of occupants

  // Return the area per person.
  public int AreaPerPerson() {
    return Area / Occupants;
  }
}
// Use the return value from AreaPerPerson().
class BuildingDemo {
  static void Main() {
    Building house = new Building();
    Building office = new Building();
    int areaPP; // area per person

    // Assign values to fields in house.
    house.Occupants = 4;
    house.Area = 2500;
    house.Floors = 2;

    // Assign values to fields in office.
    office.Occupants = 25;
    office.Area = 4200;
    office.Floors = 3;

    // Obtain area per person for house.
    areaPP = house.AreaPerPerson();
 
    Console.WriteLine("house has:\n  " +
                      house.Floors + " floors\n  " +
                      house.Occupants + " occupants\n  " +
                      house.Area + " total area\n  " +
                       areaPP + " area per person");

     Console.WriteLine();

     // Obtain area per person for office.
     areaPP = office.AreaPerPerson();

     Console.WriteLine("office has:\n  " +
                      office.Floors + " floors\n  " +
                      office.Occupants + " occupants\n  " +
                      office.Area + " total area\n  " +
                       areaPP + " area per person");
  }
}

The output is the same as shown earlier.

In the program, notice that when AreaPerPerson( ) is called, it is put on the right side of an assignment statement. On the left is a variable that will receive the value returned by AreaPerPerson( ). Thus, after

areaPP = house.AreaPerPerson();

executes, the area-per-person of the house object is stored in areaPP.

Notice that AreaPerPerson( ) now has a return type of int. This means that it will return an integer value to the caller. The return type of a method is important because the type of data returned by a method must be compatible with the return type specified by the method. Thus, if you want a method to return data of type double, then its return type must be type double.

Although the preceding program is correct, it is not written as efficiently as it could be. Specifically, there is no need for the areaPP variable. A call to AreaPerPerson( ) can be used in the WriteLine( ) statement directly, as shown here:

Console.WriteLine("house has:\n  " +
                  house.Floors + " floors\n  " +
                  house.Occupants + " occupants\n  " +
                  house.Area + " total area\n  " +
                  house.AreaPerPerson() + " area per person");

In this case, when WriteLine( ) is executed, house.AreaPerPerson( ) is called automatically, and its value will be passed to WriteLine( ). Furthermore, you can use a call to AreaPerPerson( ) whenever the area-per-person of a Building object is needed. For example, this statement compares the per-person areas of two buildings:

if(b1.AreaPerPerson() > b2.AreaPerPerson())
  Console.WriteLine("b1 has more space for each person");

Use Parameters

It is possible to pass one or more values to a method when the method is called. A value passed to a method is called an argument. Inside the method, the variable that receives the argument is called a formal parameter, or just parameter, for short. Parameters are declared inside the parentheses that follow the method’s name. The parameter declaration syntax is the same as that used for variables. The scope of a parameter is the body of its method. Aside from its special task of receiving an argument, it acts like any other local variable.

Here is a simple example that uses a parameter. Inside the ChkNum class, the method IsPrime( ) returns true if the value that it is passed is prime. It returns false otherwise. Therefore, IsPrime( ) has a return type of bool.

// A simple example that uses a parameter.

using System;

class ChkNum {
  // Return true if x is prime.
  public bool IsPrime(int x) {
    if(x <= 1) return false;

    for(int i=2; i <= x/i; i++)
      if((x %i) == 0) return false;

    return true;
  }
}

class ParmDemo {
  static void Main() {
     ChkNum ob = new ChkNum();

     for(int i=2; i < 10; i++)
       if(ob.IsPrime(i)) Console.WriteLine(i + " is prime.");
       else Console.WriteLine(i + " is not prime.");
  }
}

Here is the output produced by the program:

2 is prime.
3 is prime.
4 is not prime.
5 is prime.
6 is not prime.
7 is prime.
8 is not prime.
9 is not prime.

In the program, IsPrime( ) is called eight times, and each time a different value is passed. Let’s look at this process closely. First, notice how IsPrime( ) is called. The argument is specified between the parentheses. When IsPrime( ) is called the first time, it is passed the value 2. Thus, when IsPrime( ) begins executing, the parameter x receives the value 2. In the second call, 3 is the argument, and x then has the value 3. In the third call, the argument is 4, which is the value that x receives, and so on. The point is that the value passed as an argument when IsPrime( ) is called is the value received by its parameter, x.

A method can have more than one parameter. Simply declare each parameter, separating one from the next with a comma. For example, here the ChkNum class is expanded by adding a method called LeastComFactor( ), which returns the smallest factor that its two arguments have in common. In other words, it returns the smallest whole number value that can evenly divide both arguments.

// Add a method that takes two arguments.

using System;

class ChkNum {
  // Return true if x is prime.
  public bool IsPrime(int x) {
    if(x <= 1) return false;

    for(int i=2; i <= x/i; i++)
      if((x %i) == 0) return false;

    return true;
  }

  // Return the least common factor.
  public int LeastComFactor(int a, int b) {
    int max;

    if(IsPrime(a) || IsPrime(b)) return 1;

    max = a < b ? a : b;

    for(int i=2; i <= max/2; i++)
      if(((a%i) == 0) && ((b%i) == 0)) return i;
     return 1;
  }
}

class ParmDemo {
  static void Main() {
     ChkNum ob = new ChkNum();
     int a, b;

     for(int i=2; i < 10; i++)
       if(ob.IsPrime(i)) Console.WriteLine(i + " is prime.");
       else Console.WriteLine(i + " is not prime.");

     a = 7;
     b = 8;
     Console.WriteLine("Least common factor for " +
                       a + " and " + b + " is " +
                       ob.LeastComFactor(a, b));

     a = 100;
     b = 8;
     Console.WriteLine("Least common factor for " +
                       a + " and " + b + " is " +
                       ob.LeastComFactor(a, b));

     a = 100;
     b = 75;
     Console.WriteLine("Least common factor for " +
                       a + " and " + b + " is " +
                       ob.LeastComFactor(a, b));
  }
}

Notice that when LeastComFactor( ) is called, the arguments are also separated by commas. The output from the program is shown here:

2 is prime.
3 is prime.
4 is not prime.
5 is prime.
6 is not prime.
7 is prime.
8 is not prime.
9 is not prime.
Least common factor for 7 and 8 is 1
Least common factor for 100 and 8 is 2
Least common factor for 100 and 75 is 5

When using multiple parameters, each parameter specifies its own type, which can differ from the others. For example, this is perfectly valid:

int MyMeth(int a, double b, float c) {
 // ...

Add a Parameterized Method to Building

You can use a parameterized method to add a new feature to the Building class: the ability to compute the maximum number of occupants for a building assuming that each occupant must have a certain minimal space. This new method is called MaxOccupant( ). It is shown here:

// Return the maximum number of occupants if each
// is to have at least the specified minimum area.
public int MaxOccupant(int minArea) {
  return Area / minArea;
}

When MaxOccupant( ) is called, the parameter minArea receives the minimum space needed for each occupant. The method divides the total area of the building by this value and returns the result.

The entire Building class that includes MaxOccupant( ) is shown here:

/*
   Add a parameterized method that computes the
   maximum number of people that can occupy a
   building assuming each needs a specified
   minimum space.
*/

using System;

class Building {
  public int Floors;     // number of floors
  public int Area;      // total square footage of building
  public int Occupants; // number of occupants

  // Return the area per person.
  public int AreaPerPerson() {
    return Area / Occupants;
  }

  // Return the maximum number of occupants if each
  // is to have at least the specified minimum area.
  public int MaxOccupant(int minArea) {
    return Area / minArea;
  }
}

// Use MaxOccupant().
class BuildingDemo {
  static void Main() {
    Building house = new Building();
    Building office = new Building();

    // Assign values to fields in house.
    house.Occupants = 4;
    house.Area = 2500;
    house.Floors = 2;
    // Assign values to fields in office.
    office.Occupants = 25;
    office.Area = 4200;
    office.Floors = 3;

    Console.WriteLine("Maximum occupants for house if each has " +
                       300 + " square feet: " +
                       house.MaxOccupant(300));

    Console.WriteLine("Maximum occupants for office if each has " +
                       300 + " square feet: " +
                      office.MaxOccupant(300));
  }
}

The output from the program is shown here:

Maximum occupants for house if each has 300 square feet: 8
Maximum occupants for office if each has 300 square feet: 14

Avoiding Unreachable Code

When creating methods, you should avoid causing a situation in which a portion of code cannot, under any circumstances, be executed. This is called unreachable code, and it is considered incorrect in C#. The compiler will issue a warning message if you create a method that contains unreachable code. For example:

public void MyMeth() {
  char a, b;

  // ...
  if(a==b) {
    Console.WriteLine("equal");
    return;
  } else {
    Console.WriteLine("not equal");
    return;
  }
  Console.WriteLine("this is unreachable");
}

Here, the method MyMeth( ) will always return before the final WriteLine( ) statement is executed. If you try to compile this method, you will receive a warning. In general, unreachable code constitutes a mistake on your part, so it is a good idea to take unreachable code warnings seriously.

Constructors

In the preceding examples, the instance variables of each Building object had to be set manually using a sequence of statements, such as

house.Occupants = 4;
house.Area = 2500;
house.Floors = 2;

An approach like this would never be used in professionally written C# code. Aside from this approach being error prone (you might forget to set one of the fields), there is simply a better way to accomplish this task: the constructor.

A constructor initializes an object when it is created. It has the same name as its class and is syntactically similar to a method. However, constructors have no explicit return type. The general form of a constructor is shown here:

access class-name(param-list) {

// constructor code

}

Typically, you will use a constructor to give initial values to the instance variables defined by the class or to perform any other startup procedures required to create a fully formed object. Also, usually, access is public because constructors are normally called from outside their class. The param-list can be empty, or it can specify one or more parameters.

All classes have constructors, whether you define one or not, because C# automatically provides a default constructor that causes all member variables to be initialized to their default values. For most value types, the default value is zero. For bool, the default is false. For reference types, the default is null. However, once you define your own constructor, the default constructor is no longer used.

Here is a simple example that uses a constructor:

// A simple constructor.

using System;

class MyClass {
  public int x;

  public MyClass() {
    x = 10;
  }
}

class ConsDemo {
  static void Main() {
     MyClass t1 = new MyClass();
     MyClass t2 = new MyClass();

    Console.WriteLine(t1.x + " " + t2.x);
  }
}

In this example, the constructor for MyClass is

public MyClass() {
  x = 10;
}

Notice that the constructor is specified as public. This is because the constructor will be called from code defined outside of its class. This constructor assigns the instance variable x of MyClass the value 10. This constructor is called by new when an object is created. For example, in the line

MyClass t1 = new MyClass();

the constructor MyClass( ) is called on the t1 object, giving t1.x the value 10. The same is true for t2. After construction, t2.x has the value 10. Thus, the output from the program is

10 10

Parameterized Constructors

In the preceding example, a parameterless constructor was used. While this is fine for some situations, most often you will need a constructor that has one or more parameters. Parameters are added to a constructor in the same way they are added to a method: just declare them inside the parentheses after the constructor’s name. For example, here MyClass is given a parameterized constructor:

// A parameterized constructor.

using System;

class MyClass {
  public int x;

  public MyClass(int i) {
    x = i;
  }
}

class ParmConsDemo {
  static void Main() {
     MyClass t1 = new MyClass(10);
     MyClass t2 = new MyClass(88);

    Console.WriteLine(t1.x + " " + t2.x);
  }
}

The output from this program is shown here:

10 88

In this version of the program, the MyClass( ) constructor defines one parameter called i, which is used to initialize the instance variable, x. Thus, when the line

MyClass t1 = new MyClass(10);

executes, the value 10 is passed to i, which is then assigned to x.

Add a Constructor to the Building Class

We can improve the Building class by adding a constructor that automatically initializes the Floors, Area, and Occupants fields when an object is constructed. Pay special attention to how Building objects are created.

// Add a constructor to Building.

using System;

class Building {
  public int Floors;    // number of floors
  public int Area;      // total square footage of building
  public int Occupants; // number of occupants

  // A parameterized constructor for Building.
  public Building(int f, int a, int o) {
    Floors = f;
    Area = a;
    Occupants = o;
  }

  // Display the area per person.
  public int AreaPerPerson() {
    return Area / Occupants;
  }

  // Return the maximum number of occupants if each
  // is to have at least the specified minimum area.
  public int MaxOccupant(int minArea) {
    return Area / minArea;
  }
}

// Use the parameterized Building constructor.
class BuildingDemo {
  static void Main() {
    Building house = new Building(2, 2500, 4);
    Building office = new Building(3, 4200, 25);

    Console.WriteLine("Maximum occupants for house if each has " +
                      300 + " square feet: " +
                      house.MaxOccupant(300));

    Console.WriteLine("Maximum occupants for office if each has " +
                      300 + " square feet: " +
                      office.MaxOccupant(300));
  }
}

The output from this program is the same as for the previous version.

Both house and office were initialized by the Building( ) constructor when they were created. Each object is initialized as specified in the parameters to its constructor. For example, in the following line,

Building house = new Building(2, 2500, 4);

the values 2, 2500, and 4 are passed to the Building( ) constructor when new creates the object. Thus, house’s copy of Floors, Area, and Occupants will contain the values 2, 2500, and 4, respectively.

The new Operator Revisited

Now that you know more about classes and their constructors, let’s take a closer look at the new operator. As it relates to classes, the new operator has this general form:

new class-name(arg-list)

Here, class-name is the name of the class that is being instantiated. The class name followed by parentheses specifies the constructor for the class. If a class does not define its own constructor, new will use the default constructor supplied by C#. Thus, new can be used to create an object of any class type.

Since memory is finite, it is possible that new will not be able to allocate memory for an object because insufficient memory exists. If this happens, a runtime exception will occur. (You will learn how to handle exceptions in Chapter 13.) For the sample programs in this book, you won’t need to worry about running out of memory, but you may need to consider this possibility in real-world programs that you write.

Using new with Value Types

At this point, you might be asking why you don’t need to use new for variables of the value types, such as int or float? In C#, a variable of a value type contains its own value. Memory to hold this value is automatically provided when the program is run. Thus, there is no need to explicitly allocate this memory using new. Conversely, a reference variable stores a reference to an object. The memory to hold this object must be allocated dynamically, during execution.

Not making the fundamental types, such int or char, into reference types greatly improves your program’s performance. When using a reference type, there is a layer of indirection that adds overhead to each object access. This layer of indirection is avoided by a value type.

As a point of interest, it is permitted to use new with the value types, as shown here:

int i = new int();

Doing so invokes the default constructor for type int, which initializes i to zero. For example:

// Use new with a value type.

using System;

class newValue {
  static void Main() {
    int i = new int(); // initialize i to zero
    Console.WriteLine("The value of i is: " + i);
  }
}

The output from this program is

The value of i is: 0

As the output verifies, i is initialized to zero. Remember, without the use of new, i would be uninitialized, and it would cause an error to attempt to use it in the WriteLine( ) statement without explicitly giving it a value first.

In general, invoking new for a value type invokes the default constructor for that type. It does not, however, dynamically allocate memory. Frankly, most programmers do not use new with the value types.

Garbage Collection and Destructors

As you have seen, objects are dynamically allocated from a pool of free memory by using the new operator. Of course, memory is not infinite, and the free memory can be exhausted. Thus, it is possible for new to fail because there is insufficient free memory to create the desired object. For this reason, one of the key components of any dynamic allocation scheme is the recovery of free memory from unused objects, making that memory available for subsequent reallocation. In many programming languages, the release of previously allocated memory is handled manually. For example, in C++, the delete operator is used to free memory that was allocated. However, C# uses a different, more trouble-free approach: garbage collection.

C#’s garbage collection system reclaims objects automatically—occurring transparently, behind the scenes, without any programmer intervention. It works like this: When no references to an object exist, that object is assumed to be no longer needed, and the memory occupied by the object is eventually released and collected. This recycled memory can then be used for a subsequent allocation.

Garbage collection occurs only sporadically during the execution of your program. It will not occur simply because one or more objects exist that are no longer used. Thus, you can’t know, or make assumptions about, precisely when garbage collection will take place.

Destructors

It is possible to define a method that will be called just prior to an object’s final destruction by the garbage collector. This method is called a destructor, and it can be used in some highly specialized situations to ensure that an object terminates cleanly. For example, you might use a destructor to ensure that a system resource owned by an object is released. It must be stated at the outset that destructors are a very advanced feature that are applicable only to certain rare cases. They are not normally needed. They are briefly described here for completeness.

Destructors have this general form:

~class-name( ) {

// destruction code

}

Here, class-name is the name of the class. Thus, a destructor is declared like a constructor except that it is preceded with a ~ (tilde). Notice it has no return type and takes no arguments.

To add a destructor to a class, you simply include it as a member. It is called whenever an object of its class is about to be recycled. Inside the destructor, you will specify those actions that must be performed before an object is destroyed.

It is important to understand that the destructor is called just prior to garbage collection. It is not called when a variable containing a reference to an object goes out of scope, for example. (This differs from destructors in C++, which are called when an object goes out of scope.) This means that you cannot know precisely when a destructor will be executed. Furthermore, it is possible for your program to end before garbage collection occurs, so a destructor might not get called at all.

The following program demonstrates a destructor. It works by creating and destroying a large number of objects. During this process, at some point the garbage collector will be activated, and the destructors for the objects will be called.

// Demonstrate a destructor.

using System;

class Destruct {
  public int x;

  public Destruct(int i) {
    x = i;
  }

  // Called when object is recycled.
  ~Destruct() {
    Console.WriteLine("Destructing " + x);
  }

  // Generates an object that is immediately destroyed.
  public void Generator(int i) {
    Destruct o = new Destruct(i);
  }
}

class DestructDemo {
  static void Main() {
    int count;

    Destruct ob = new Destruct(0);

    /* Now, generate a large number of objects. At
       some point, garbage collection will occur.
       Note: You might need to increase the number
       of objects generated in order to force
       garbage collection. */

    for(count=1; count < 100000; count++)
      ob.Generator(count);

    Console.WriteLine("Done");
  }
}

Here is how the program works. The constructor sets the instance variable x to a known value. In this example, x is used as an object ID. The destructor displays the value of x when an object is recycled. Of special interest is Generator( ). This method creates and then promptly destroys a Destruct object. The DestructDemo class creates an initial Destruct object called ob. Then using ob, it creates 100,000 objects by calling Generator( ) on ob. This has the net effect of creating and destroying 100,000 objects. At various points in the middle of this process, garbage collection will take place. Precisely how often or when is dependent upon several factors, such as the initial amount of free memory, the operating system, and so on. However, at some point, you will start to see the messages generated by the destructor. If you don’t see the messages prior to program termination (that is, before you see the “Done” message), try increasing the number of objects being generated by upping the count in the for loop.

One important point: The call to WriteLine( ) inside ~Destruct( ) is purely for the sake of illustration in this rather contrived example. Normally, a destructor should act only on the instance variables defined by its class.

Because of the nondeterministic way in which destructors are called, they should not be used to perform actions that must occur at a specific point in your program. One other point: It is possible to request garbage collection. This is described in Part II, when C#’s class library is discussed. However, manually initiating garbage collection is not recommended for most circumstances, because it can lead to inefficiencies. Also, because of the way the garbage collector works, even if you explicitly request garbage collection, there is no way to know precisely when a specific object will be recycled.

The this Keyword

Before concluding this chapter, it is necessary to introduce this. When a method is called, it is automatically passed a reference to the invoking object (that is, the object on which the method is called). This reference is called this. Therefore, this refers to the object on which the method is acting. To understand this, first consider a program that creates a class called Rect that encapsulates the width and height of a rectangle and that includes a method called Area( ) that returns its area.

using System;

class Rect {
  public int Width;
  public int Height;

  public Rect(int w, int h) {
    Width = w;
    Height = h;
  }

  public int Area() {
    return Width * Height;
  }
}

class UseRect {
  static void Main() {
     Rect r1 = new Rect(4, 5);
     Rect r2 = new Rect(7, 9);

     Console.WriteLine("Area of r1: " + r1.Area());

     Console.WriteLine("Area of r2: " + r2.Area());
  }
}

As you know, within a method, the other members of a class can be accessed directly, without any object or class qualification. Thus, inside Area( ), the statement

return Width * Height;

means that the copies of Width and Height associated with the invoking object will be multiplied together and the result returned. However, the same statement can also be written like this:

return this.Width * this.Height;

Here, this refers to the object on which Area( ) was called. Thus, this.Width refers to that object’s copy of Width, and this.Height refers to that object’s copy of Height. For example, if Area( ) had been invoked on an object called x, then this in the preceding statement would have been referring to x. Writing the statement without using this is really just shorthand.

It is also possible to use this inside a constructor. In this case, this refers to the object that is being constructed. For example, inside Rect( ), the statements

Width = w;
Height = h;

can be written like this:

this.Width = w;
this.Height = h;

Of course, there is no benefit in doing so in this case.

For the sake of illustration, here is the entire Rect class written using the this reference:

using System;

class Rect {
  public int Width;
  public int Height;

  public Rect(int w, int h) {
    this.Width = w;
    this.Height = h;
  }

  public int Area() {
    return this.Width * this.Height;
  }
}

class UseRect {
  static void Main() {
     Rect r1 = new Rect(4, 5);
     Rect r2 = new Rect(7, 9);

     Console.WriteLine("Area of r1: " + r1.Area());

     Console.WriteLine("Area of r2: " + r2.Area());
  }
}

Actually, no C# programmer would use this as just shown because nothing is gained and the standard form is easier. However, this has some important uses. For example, the C# syntax permits the name of a parameter or a local variable to be the same as the name of an instance variable. When this happens, the local name hides the instance variable. You can gain access to the hidden instance variable by referring to it through this. For example, the following is a syntactically valid way to write the Rect( ) constructor:

public Rect(int Width, int Height) {
  this.Width = Width;
  this.Height = Height;
}

In this version, the names of the parameters are the same as the names of the instance variables, thus hiding them. However, this is used to “uncover” the instance variables.