Chapter 7. Methods: A Deeper Look

 

E pluribus unum. (One composed of many.)

 
 --Virgil
 

O! call back yesterday, bid time return.

 
 --William Shakespeare
 

Call me Ishmael.

 
 --Herman Melville
 

Answer me in one word.

 
 --William Shakespeare
 

There is a point at which methods devour themselves.

 
 --Frantz Fanon
<feature> <supertitle>Objectives</supertitle>

In this chapter you’ll learn:

<objective>

How static methods and variables are associated with classes rather than objects.

</objective>
<objective>

How the method call/return mechanism is supported by the method-call stack.

</objective>
<objective>

How to use random-number generation to implement game-playing applications.

</objective>
<objective>

How the visibility of declarations is limited to specific parts of applications.

</objective>
<objective>

How to create overloaded methods.

</objective>
<objective>

How to use optional and named parameters.

</objective>
<objective>

What recursive methods are.

</objective>
<objective>

Passing method arguments by value and by reference.

</objective>
</feature>
<feature> <supertitle>Outline</supertitle> </feature>

Introduction

Most computer applications that solve real-world problems are much larger than the applications presented in this book’s first few chapters. Experience has shown that the best way to develop and maintain a large application is to construct it from small, simple pieces. This technique is called divide and conquer. We introduced methods in Chapter 4. In this chapter we study methods in more depth. We emphasize how to declare and use methods to facilitate the design, implementation, operation and maintenance of large applications.

You’ll see that it’s possible for certain methods, called static methods, to be called without the need for an object of the class to exist. You’ll learn how to declare a method with more than one parameter. You’ll also learn how C# is able to keep track of which method is currently executing, how value-type and reference-type arguments are passed to methods, how local variables of methods are maintained in memory and how a method knows where to return after it completes execution.

We discuss simulation techniques with random-number generation and develop a version of the casino dice game called craps that uses most of the programming techniques you’ve learned to this point in the book. In addition, you’ll learn to declare values that cannot change (i.e., constants). You’ll also learn to write methods that call themselves—this is called recursion.

Many of the classes you’ll use or create while developing applications will have more than one method of the same name. This technique, called method overloading, is used to implement methods that perform similar tasks but with different types and/or different numbers of arguments.

Packaging Code in C#

Common ways of packaging code are properties, methods, classes and namespaces. C# applications are written by combining new properties, methods and classes that you write with predefined properties, methods and classes available in the .NET Framework Class Library and in various other class libraries. Related classes are often grouped into namespaces and compiled into class libraries so that they can be reused in other applications. You’ll learn how to create your own namespaces and class libraries in Chapter 15. The Framework Class Library provides many predefined classes that contain methods for performing common mathematical calculations, string manipulations, character manipulations, input/output operations, database operations, networking operations, file processing, error checking, web-application development and more.

Good Programming Practice 7.1

Good Programming Practice 7.1

Familiarize yourself with the classes and methods provided by the Framework Class Library (msdn.microsoft.com/en-us/library/ms229335.aspx).

Software Engineering Observation 7.1

Software Engineering Observation 7.1

Don’t try to “reinvent the wheel.” When possible, reuse Framework Class Library classes and methods. This reduces application development time, avoids introducing programming errors and contributes to good application performance.

Methods (called functions or procedures in other programming languages) allow you to modularize an application by separating its tasks into self-contained units. You’ve declared methods in every application you’ve written. These methods are sometimes referred to as user-defined methods. The actual statements in the method bodies are written only once, can be reused from several locations in an application and are hidden from other methods.

There are several motivations for modularizing an application by means of methods. One is the “divide-and-conquer” approach, which makes application development more manageable by constructing applications from small, simple pieces. Another is software reusability—existing methods can be used as building blocks to create new applications. Often, you can create applications mostly by reusing existing methods rather than by building customized code. For example, in earlier applications, we did not have to define how to read data values from the keyboard—the Framework Class Library provides these capabilities in class Console. A third motivation is to avoid repeating code. Dividing an application into meaningful methods makes the application easier to debug and maintain.

Software Engineering Observation 7.2

Software Engineering Observation 7.2

To promote software reusability, every method should be limited to performing a single, well-defined task, and the name of the method should express that task effectively. Such methods make applications easier to write, debug, maintain and modify.

Software Engineering Observation 7.3

Software Engineering Observation 7.3

If you cannot choose a concise name that expresses a method’s task, your method might be attempting to perform too many diverse tasks. It’s usually best to break such a method into several smaller methods.

As you know, a method is invoked by a method call, and when the called method completes its task, it returns a result or simply returns control to the caller. The code that calls a method is also sometimes known as the client code—as it’s a client of the method. An analogy to the method-call-and-return structure is the hierarchical form of management (Figure 7.1). A boss (the caller) asks a worker (the called method) to perform a task and report back (i.e., return) the results after completing the task. The boss method does not know how the worker method performs its designated tasks. The worker may also call other worker methods, unbeknown to the boss. This “hiding” of implementation details promotes good software engineering. Figure 7.1 shows the boss method communicating with several worker methods in a hierarchical manner. The boss method divides the responsibilities among the various worker methods. Note that worker1 acts as a “boss method” to worker4 and worker5.

Hierarchical boss-method/worker-method relationship.

Figure 7.1. Hierarchical boss-method/worker-method relationship.

static Methods, static Variables and Class Math

Although most methods execute on specific objects in response to method calls, this is not always the case. Sometimes a method performs a task that does not depend on the contents of any object. Such a method applies to the class in which it’s declared as a whole and is known as a static method. It’s not uncommon for a class to contain a group of static methods to perform common tasks. For example, recall that we used static method Pow of class Math to raise a value to a power in Fig. 6.6. To declare a method as static, place the keyword static before the return type in the method’s declaration. You call any static method by specifying the name of the class in which the method is declared, followed by the member access (.) operator and the method name, as in

ClassName.MethodName( arguments )

We use various methods of the Math class here to present the concept of static methods. Class Math (from the System namespace) provides a collection of methods that enable you to perform common mathematical calculations. For example, you can calculate the square root of 900.0 with the static method call

Math.Sqrt( 900.0 )

The preceding expression evaluates to 30.0. Method Sqrt takes an argument of type double and returns a result of type double. To output the value of the preceding method call in the console window, you might write the statement

Console.WriteLine( Math.Sqrt( 900.0 ) );

In this statement, the value that Sqrt returns becomes the argument to method WriteLine. We did not create a Math object before calling method Sqrt. Also, all of Math’s methods are static—therefore, each is called by preceding the name of the method with the class name Math and the member access (.) operator. Similarly, Console method WriteLine is a static method of class Console, so we invoke the method by preceding its name with the class name Console and the member access (.) operator.

Method arguments may be constants, variables or expressions. If c = 13.0, d = 3.0 and f = 4.0, then the statement

Console.WriteLine( Math.Sqrt( c + d * f ) );

calculates and displays the square root of 13.0 + 3.0 * 4.0 = 25.0—namely, 5.0. Figure 7.2 summarizes several Math class methods. In the figure, x and y are of type double.

Table 7.2. Math class methods.

Method

Description

Example

Abs( x )

absolute value of x

Abs( 23.7 ) is 23.7

Abs( 0.0 ) is 0.0

Abs( -23.7 ) is 23.7

Ceiling( x )

rounds x to the smallest integer not less than x

Ceiling( 9.2 ) is 10.0

Ceiling( -9.8 ) is -9.0

Cos( x )

trigonometric cosine of x (x in radians)

Cos( 0.0 ) is 1.0

Exp( x )

exponential method ex

Exp( 1.0 ) is 2.71828

Exp( 2.0 ) is 7.38906

Floor( x )

rounds x to the largest integer not greater than x

Floor( 9.2 ) is 9.0

Floor( -9.8 ) is -10.0

Log( x )

natural logarithm of x (base e)

Log( Math.E ) is 1.0

Log( Math.E * Math.E ) is 2.0

Max( x, y )

larger value of x and y

Max( 2.3, 12.7 ) is 12.7

Max( -2.3, -12.7 ) is -2.3

Min( x, y )

smaller value of x and y

Min( 2.3, 12.7 ) is 2.3

Min( -2.3, -12.7 ) is -12.7

Pow( x, y )

x raised to the power y (i.e., xy)

Pow( 2.0, 7.0 ) is 128.0

Pow( 9.0, 0.5 ) is 3.0

Sin( x )

trigonometric sine of x (x in radians)

Sin( 0.0 ) is 0.0

Sqrt( x )

square root of x

Sqrt( 900.0 ) is 30.0

Tan( x )

trigonometric tangent of x (x in radians)

Tan( 0.0 ) is 0.0

Math Class Constants PI and E

Class Math also declares two static constants that represent commonly used mathematical values: Math.PI and Math.E. The constant Math.PI (3.14159265358979323846) is the ratio of a circle’s circumference to its diameter. The constant Math.E (2.7182818284590452354) is the base value for natural logarithms (calculated with static Math method Log). These constants are declared in class Math with the modifiers public and const. Making them public allows other programmers to use these variables in their own classes. A constant is declared with the keyword const—its value cannot be changed after the constant is declared. Both PI and E are declared const because their values never change.

Common Programming Error 7.1

Common Programming Error 7.1

Every constant declared in a class, but not inside a method of the class is implicitly static, so it’s a syntax error to declare such a constant with keyword static explicitly.

Because these constants are static, you can access them via the class name Math and the member access (.) operator, just like class Math’s methods. Recall from Section 4.5 that when each object of a class maintains its own copy of an attribute, the variable that represents the attribute is also known as an instance variable—each object (instance) of the class has a separate instance of the variable. There are also variables for which each object of a class does not have a separate instance of the variable. That’s the case with static variables. When objects of a class containing static variables are created, all the objects of that class share one copy of the class’s static variables. Together the static variables and instance variables represent the fields of a class.

Why Is Method Main Declared static?

Why must Main be declared static? During application startup, when no objects of the class have been created, the Main method must be called to begin program execution. The Main method is sometimes called the application’s entry point. Declaring Main as static allows the execution environment to invoke Main without creating an instance of the class. Method Main is often declared with the header:

public static void Main( string args[] )

When you execute your application from the command line, you type the application name, as in

ApplicationName argument1 argument2 ...

In the preceding command, argument1 and argument2 are the command-line arguments to the application that specify a list of strings (separated by spaces) the execution environment will pass to the Main method of your application. Such arguments might be used to specify options (e.g., a file name) to run the application. As you’ll learn in Chapter 8, Arrays, your application can access those command-line arguments and use them to customize the application.

Additional Comments about Method Main

The header of a Main method does not need to appear exactly as we’ve shown. Applications that do not take command-line arguments may omit the string[] args parameter. The public keyword may also be omitted. In addition, you can declare Main with return type int (instead of void) to enable Main to return an error code with the return statement. A Main method declared with any one of these headers can be used as the application’s entry point—but you can declare only one such Main method in each class.

In earlier chapters, most applications had one class that contained only Main, and some examples had a second class that was used by Main to create and manipulate objects. Actually, any class can contain a Main method. In fact, each of our two-class examples could have been implemented as one class. For example, in the application in Figs. 6.9 and 6.10, method Main (lines 6–16 of Fig. 6.10) could have been taken as is and placed in class GradeBook (Fig. 6.9). The application results would have been identical to those of the two-class version. You can place a Main method in every class you declare. Some programmers take advantage of this to build a small test application into each class they declare. However, if you declare more than one Main method among the classes of your project, you’ll need to indicate to the IDE which one you would like to be the application’s entry point. You can do this by selecting Project > [ProjectName] Properties... (where [Project-Name] is the name of your project) and selecting the class containing the Main method that should be the entry point from the Startup object list box.

Declaring Methods with Multiple Parameters

Methods often require more than one piece of information to perform their tasks. We now consider how to write your own methods with multiple parameters.

The application in Fig. 7.3 uses a user-defined method called Maximum to determine and return the largest of three double values that are input by the user. When the application begins execution, the Main method (lines 8–22) executes. Lines 11–15 prompt the user to enter three double values and read them from the user. Line 18 calls method Maximum (declared in lines 25–38) to determine the largest of the three double values passed as arguments to the method. When method Maximum returns the result to line 18, the application assigns Maximum’s return value to local variable result. Then line 21 outputs result. At the end of this section, we’ll discuss the use of operator + in line 21.

Example 7.3. User-defined method Maximum.

 1   // Fig. 7.3: MaximumFinder.cs
 2   // User-defined method Maximum.
 3   using System;
 4
 5   public class MaximumFinder
 6   {
 7      // obtain three floating-point values and determine maximum value
 8      public static void Main( string[] args )
 9      {
10         // prompt for and input three floating-point values
11         Console.WriteLine( "Enter three floating-point values,
"
12            + "  pressing 'Enter' after each one: " );
13         double number1 = Convert.ToDouble( Console.ReadLine() );
14         double number2 = Convert.ToDouble( Console.ReadLine() );
15         double number3 = Convert.ToDouble( Console.ReadLine() );
16
17         // determine the maximum value
18         double result = Maximum( number1, number2, number3 );
19
20         // display maximum value
21         Console.WriteLine( "Maximum is: " + result );
22      } // end Main
23
24      // returns the maximum of its three double parameters         
25      public static double Maximum( double x, double y, double z )  
26      {                                                             
27         double maximumValue = x; // assume x is the largest to start
28                                                                     
29         // determine whether y is greater than maximumValue         
30         if ( y > maximumValue )                                     
31            maximumValue = y;                                        
32                                                                     
33         // determine whether z is greater than maximumValue         
34         if ( z > maximumValue )                                     
35            maximumValue = z;                                        
36                                                                     
37         return maximumValue;                                        
38      } // end method Maximum                                        
39   } // end class MaximumFinder

Enter three floating-point values,
  pressing 'Enter' after each one:
3.33
2.22
1.11
Maximum is: 3.33
Enter three floating-point values,
  pressing 'Enter' after each one:
2.22
3.33
1.11
Maximum is: 3.33
Enter three floating-point values,
  pressing 'Enter' after each one:
1.11
2.22
867.5309
Maximum is: 867.5309

The public and static Keywords

Method Maximum’s declaration begins with keyword public to indicate that the method is “available to the public”—it can be called from methods of other classes. The keyword static enables the Main method (another static method) to call Maximum as shown in line 18 without qualifying the method name with the class name MaximumFinderstatic methods in the same class can call each other directly. Any other class that uses Maximum must fully qualify the method name with the class name.

Method Maximum

Consider the declaration of method Maximum (lines 25–38). Line 25 indicates that the method returns a double value, that the method’s name is Maximum and that the method requires three double parameters (x, y and z) to accomplish its task. When a method has more than one parameter, the parameters are specified as a comma-separated list. When Maximum is called in line 18, the parameter x is initialized with the value of the argument number1, the parameter y is initialized with the value of the argument number2 and the parameter z is initialized with the value of the argument number3. There must be one argument in the method call for each required parameter (sometimes called a formal parameter) in the method declaration. Also, each argument must be consistent with the type of the corresponding parameter. For example, a parameter of type double can receive values like 7.35 (a double), 22 (an int) or –0.03456 (a double), but not strings like "hello". Section 7.7 discusses the argument types that can be provided in a method call for each parameter of a simple type.

To determine the maximum value, we begin with the assumption that parameter x contains the largest value, so line 27 declares local variable maximumValue and initializes it with the value of parameter x. Of course, it’s possible that parameter y or z contains the largest value, so we must compare each of these values with maximumValue. The if statement at lines 30–31 determines whether y is greater than maximumValue. If so, line 31 assigns y to maximumValue. The if statement at lines 34–35 determines whether z is greater than maximumValue. If so, line 35 assigns z to maximumValue. At this point, the largest of the three values resides in maximumValue, so line 37 returns that value to line 18. When program control returns to the point in the application where Maximum was called, Maximum’s parameters x, y and z are no longer accessible. Methods can return at most one value; the returned value can be a value type that contains many values (implemented as a struct) or a reference to an object that contains many values.

Variable result is a local variable in method Main because it’s declared in the block that represents the method’s body. Variables should be declared as fields of a class (i.e., as either instance variables or static variables of the class) only if they’re required for use in more than one method of the class or if the application should save their values between calls to the class’s methods.

Common Programming Error 7.2

Common Programming Error 7.2

Declaring method parameters of the same type as float x, y instead of float x, float y is a syntax error—a type is required for each parameter in the parameter list.

Implementing Method Maximum by Reusing Method Math.Max

Recall from Fig. 7.2 that class Math has a Max method that can determine the larger of two values. The entire body of our maximum method could also be implemented with nested calls to Math.Max, as follows:

return Math.Max( x, Math.Max( y, z ) );

The leftmost call to Math.Max specifies arguments x and Math.Max( y, z ). Before any method can be called, all its arguments must be evaluated to determine their values. If an argument is a method call, the method call must be performed to determine its return value. So, in the preceding statement, Math.Max( y, z ) is evaluated first to determine the maximum of y and z. Then the result is passed as the second argument to the other call to Math.Max, which returns the larger of its two arguments. Using Math.Max in this manner is a good example of software reuse—we find the largest of three values by reusing Math.Max, which finds the larger of two values. Note how concise this code is compared to lines 27–37 of Fig. 7.3.

Assembling Strings with String Concatenation

C# allows string objects to be created by assembling smaller strings into larger strings using operator + (or the compound assignment operator +=). This is known as string concatenation. When both operands of operator + are string objects, operator + creates a new string object in which a copy of the characters of the right operand is placed at the end of a copy of the characters in the left operand. For example, the expression "hello " + "there" creates the string "hello there" without disturbing the original strings.

In line 21 of Fig. 7.3, the expression "Maximum is: " + result uses operator + with operands of types string and double. Every value of a simple type in C# has a string representation. When one of the + operator’s operands is a string, the other is implicitly converted to a string, then the two are concatenated. In line 21, the double value is implicitly converted to its string representation and placed at the end of the string "Maximum is: ". If there are any trailing zeros in a double value, these will be discarded when the number is converted to a string. Thus, the number 9.3500 would be represented as 9.35 in the resulting string.

For values of simple types used in string concatenation, the values are converted to strings. If a bool is concatenated with a string, the bool is converted to the string "True" or "False" (each is capitalized). All objects have a ToString method that returns a string representation of the object. When an object is concatenated with a string, the object’s ToString method is implicitly called to obtain the string representation of the object. If the object is null, an empty string is written.

Line 21 of Fig. 7.3 could also be written using string formatting as

Console.WriteLine( "Maximum is: {0}", result );

As with string concatenation, using a format item to substitute an object into a string implicitly calls the object’s ToString method to obtain the object’s string representation. You’ll learn more about method ToString in Chapter 8.

When a large string literal is typed into an application’s source code, you can break that string into several smaller strings and place them on multiple lines for readability. The strings can be reassembled using either string concatenation or string formatting. We discuss the details of strings in Chapter 16.

Common Programming Error 7.3

Common Programming Error 7.3

It’s a syntax error to break a string literal across multiple lines in an application. If a string does not fit on one line, split the string into several smaller strings and use concatenation to form the desired string.C# also provides so-called verbatim string literals, which are preceded by the @ character. Such literals can be split over multiple lines and the characters in the literal are processed exactly as they appear in the literal.

Common Programming Error 7.4

Common Programming Error 7.4

Confusing the + operator used for string concatenation with the + operator used for addition can lead to strange results. The + operator is left-associative. For example, if integer variable y has the value 5, the expression "y + 2 = " + y + 2 results in the string "y + 2 = 52", not "y + 2 = 7", because first the value of y (5) is concatenated with the string "y + 2 = ", then the value 2 is concatenated with the new larger string "y + 2 = 5". The expression "y + 2 = " + (y + 2) produces the desired result "y + 2 = 7".

Notes on Declaring and Using Methods

You’ve seen three ways to call a method:

  1. Using a method name by itself to call a method of the same class—such as Maximum(number1, number2, number3) in line 18 of Fig. 7.3.

  2. Using a variable that contains a reference to an object, followed by the member access (.) operator and the method name to call a non-static method of the referenced object—such as the method call in line 13 of Fig. 6.10, myGrade-Book.DisplayMessage(), which calls a method of class GradeBook from the Main method of GradeBookTest.

  3. Using the class name and the member access (.) operator to call a static method of a class—such as Convert.ToDouble(Console.ReadLine()) in lines 13–15 of Fig. 7.3 or Math.Sqrt(900.0) in Section 7.3.

A static method can call only other static methods of the same class directly (i.e., using the method name by itself) and can manipulate only static variables in the same class directly. To access the class’s non-static members, a static method must use a reference to an object of the class. Recall that static methods relate to a class as a whole, whereas non-static methods are associated with a specific instance (object) of the class and may manipulate the instance variables of that object. Many objects of a class, each with its own copies of the instance variables, may exist at the same time. Suppose a static method were to invoke a non-static method directly. How would the method know which object’s instance variables to manipulate? What would happen if no objects of the class existed at the time the non-static method was invoked? Thus, C# does not allow a static method to access non-static members of the same class directly.

There are three ways to return control to the statement that calls a method. If the method does not return a result, control returns when the program flow reaches the method-ending right brace or when the statement

return;

is executed. If the method returns a result, the statement

return expression;

evaluates the expression, then returns the result (and control) to the caller.

Common Programming Error 7.5

Common Programming Error 7.5

Declaring a method outside the body of a class declaration or inside the body of another method is a syntax error.

Common Programming Error 7.6

Common Programming Error 7.6

Omitting the return type in a method declaration is a syntax error.

Common Programming Error 7.7

Common Programming Error 7.7

Redeclaring a method parameter as a local variable in the method’s body is a compilation error.

Common Programming Error 7.8

Common Programming Error 7.8

Forgetting to return a value from a method that should return one is a compilation error. If a return type other than void is specified, the method must contain a return statement in each possible execution path through the method and each return statement must return a value consistent with the method’s return type. Returning a value from a method whose return type has been declared void is a compilation error.

Method-Call Stack and Activation Records

To understand how C# performs method calls, we first need to consider a data structure (i.e., collection of related data items) known as a stack (we discuss data structures in more detail in Chapters 2023). You can think of a stack as analogous to a pile of dishes. When a dish is placed on the pile, it’s normally placed at the top (referred to as pushing the dish onto the stack). Similarly, when a dish is removed from the pile, it’s always removed from the top (referred to as popping the dish off the stack). Stacks are known as last-in, first-out (LIFO) data structures—the last item pushed (inserted) on the stack is the first item popped off (removed from) the stack.

When an application calls a method, the called method must know how to return to its caller, so the return address of the calling method is pushed onto the program-execution stack (sometimes referred to as the method-call stack). If a series of method calls occurs, the successive return addresses are pushed onto the stack in last-in, first-out order so that each method can return to its caller.

The program-execution stack also contains the memory for the local variables used in each invocation of a method during an application’s execution. This data, stored as a portion of the program-execution stack, is known as the activation record or stack frame of the method call. When a method call is made, the activation record for it is pushed onto the program-execution stack. When the method returns to its caller, the activation record for this method call is popped off the stack, and those local variables are no longer known to the application. If a local variable holding a reference to an object is the only variable in the application with a reference to that object, when the activation record containing that local variable is popped off the stack, the object can no longer be accessed by the application and will eventually be deleted from memory during “garbage collection.” We’ll discuss garbage collection in Section 10.8.

Of course, the amount of memory in a computer is finite, so only a certain amount of memory can be used to store activation records on the program-execution stack. If more method calls occur than can have their activation records stored on the program-execution stack, an error known as a stack overflow occurs.

Argument Promotion and Casting

Another important feature of method calls is argument promotion—implicitly converting an argument’s value to the type that the method expects to receive in its corresponding parameter. For example, an application can call Math method Sqrt with an integer argument even though the method expects to receive a double argument. The statement

Console.WriteLine( Math.Sqrt( 4 ) );

correctly evaluates Math.Sqrt(4) and displays the value 2.0. Sqrt’s parameter list causes C# to convert the int value 4 to the double value 4.0 before passing the value to Sqrt. Such conversions may lead to compilation errors if C#’s promotion rules are not satisfied. The promotion rules specify which conversions are allowed—that is, which conversions can be performed without losing data. In the Sqrt example above, an int is converted to a double without changing its value. However, converting a double to an int truncates the fractional part of the double value—thus, part of the value is lost. Also, double variables can hold values much larger (and much smaller) than int variables, so assigning a double to an int can cause a loss of information when the double value doesn’t fit in the int. Converting large integer types to small integer types (e.g., long to int) can also result in changed values.

The promotion rules apply to expressions containing values of two or more simple types and to simple-type values passed as arguments to methods. Each value is promoted to the appropriate type in the expression. (Actually, the expression uses a temporary copy of each value—the types of the original values remain unchanged.) Figure 7.4 lists the simple types alphabetically and the types to which each can be promoted. Values of all simple types can also be implicitly converted to type object. We demonstrate such implicit conversions in Chapter 21, Data Structures.

Table 7.4. Implicit conversions between simple types.

Type

Conversion types

bool

no possible implicit conversions to other simple types

byte

ushort, short, uint, int, ulong, long, decimal, float or double

char

ushort, int, uint, long, ulong, decimal, float or double

decimal

no possible implicit conversions to other simple types

double

no possible implicit conversions to other simple types

float

double

int

long, decimal, float or double

long

decimal, float or double

sbyte

short, int, long, decimal, float or double

short

int, long, decimal, float or double

uint

ulong, long, decimal, float or double

ulong

decimal, float or double

ushort

uint, int, ulong, long, decimal, float or double

By default, C# does not allow you to implicitly convert values between simple types if the target type cannot represent the value of the original type (e.g., the int value 2000000 cannot be represented as a short, and any floating-point number with digits after its decimal point cannot be represented in an integer type such as long, int or short). Therefore, to prevent a compilation error in cases where information may be lost due to an implicit conversion between simple types, the compiler requires you to use a cast operator to explicitly force the conversion. This enables you to “take control” from the compiler. You essentially say, “I know this conversion might cause loss of information, but for my purposes here, that’s fine.” Suppose you create a method Square that calculates the square of an integer and thus requires an int argument. To call Square with the whole part of a double argument named doubleValue, you’d write Square( (int) doubleValue ). This method call explicitly casts (converts) the value of doubleValue to an integer for use in method Square. Thus, if doubleValue’s value is 4.5, the method receives the value 4 and returns 16, not 20.25 (which does, unfortunately, result in the loss of information).

Common Programming Error 7.9

Common Programming Error 7.9

Converting a simple-type value to a value of another simple type may change the value if the promotion is not allowed. For example, converting a floating-point value to an integral value may introduce truncation errors (loss of the fractional part) in the result.

The .NET Framework Class Library

Many predefined classes are grouped into categories of related classes called namespaces. Together, these namespaces are referred to as the .NET Framework Class Library.

Throughout the text, using directives allow us to use library classes from the Framework Class Library without specifying their fully qualified names. For example, an application includes the declaration

using System;

in order to use the class names from the System namespace without fully qualifying their names. This allows you to use the unqualified class name Console, rather than the fully qualified class name System.Console, in your code. A great strength of C# is the large number of classes in the namespaces of the .NET Framework Class Library. Some key Framework Class Library namespaces are described in Fig. 7.5, which represents only a small portion of the reusable classes in the .NET Framework Class Library.

Table 7.5. .NET Framework Class Library namespaces (a subset).

Namespace

Description

System.Windows.Forms

Contains the classes required to create and manipulate GUIs. (Various classes in this namespace are discussed in Chapter 14, Graphical User Interfaces with Windows Forms: Part 1, and Chapter 15, Graphical User Interfaces with Windows Forms: Part 2.)

System.Windows.Controls
System.Windows.Input
System.Windows.Media
System.Windows.Shapes

Contain the classes of the Windows Presentation Foundation for GUIs, 2-D and 3-D graphics, multimedia and animation. (You’ll learn more about these namespaces in Chapter 24, GUI with Windows Presentation Foundation, Chapter 25, WPF Graphics and Multimedia and Chapter 29, Silverlight and Rich Internet Applications.)

System.Linq

Contains the classes that support Language Integrated Query (LINQ). (You’ll learn more about this namespace in Chapter 9, Introduction to LINQ and the List Collection, and several other chapters throughout the book.)

System.Data
System.Data.Linq

Contain the classes for manipulating data in databases (i.e., organized collections of data), including support for LINQ to SQL. (You’ll learn more about these namespaces in Chapter 18, Databases and LINQ.)

System.IO

Contains the classes that enable programs to input and output data. (You’ll learn more about this namespace in Chapter 17, Files and Streams.)

System.Web

Contains the classes used for creating and maintaining web applications, which are accessible over the Internet. (You’ll learn more about this namespace in Chapter 19, Web App Development with ASP.NET.)

System.Xml.Linq

Contains the classes that support Language Integrated Query (LINQ) for XML documents. (You’ll learn more about this namespace in Chapter 26, XML and LINQ to XML, and several other chapters throughout the book.)

System.Xml

Contains the classes for creating and manipulating XML data. Data can be read from or written to XML files. (You’ll learn more about this namespace in Chapter 26.)

System.Collections
System.Collections.Generic

Contain the classes that define data structures for maintaining collections of data. (You’ll learn more about these namespaces in Chapter 23, Collections.)

System.Text

Contains the classes that enable programs to manipulate characters and strings. (You’ll learn more about this namespace in Chapter 16, Strings and Characters.)

The set of namespaces available in the .NET Framework Class Library is quite large. Besides those summarized in Fig. 7.5, the .NET Framework Class Library contains namespaces for complex graphics, advanced graphical user interfaces, printing, advanced networking, security, database processing, multimedia, accessibility (for people with disabilities) and many other capabilities—over 100 namespaces in all.

You can locate additional information about a predefined C# class’s methods in the .NET Framework Class Library reference (msdn.microsoft.com/en-us/library/ms229335.aspx). When you visit this site, you’ll see an alphabetical listing of all the namespaces in the Framework Class Library. Locate the namespace and click its link to see an alphabetical listing of all its classes, with a brief description of each. Click a class’s link to see a more complete description of the class. Click the Methods link in the left-hand column to see a listing of the class’s methods.

Good Programming Practice 7.2

Good Programming Practice 7.2

The online .NET Framework documentation is easy to search and provides many details about each class. As you learn each class in this book, you should review the class in the online documentation for additional information.

Case Study: Random-Number Generation

In this and the next section, we develop a nicely structured game-playing application with multiple methods. The application uses most of the control statements presented thus far in the book and introduces several new programming concepts.

There’s something in the air of a casino that invigorates people—from the high rollers at the plush mahogany-and-felt craps tables to the quarter poppers at the one-armed bandits. It’s the element of chance, the possibility that luck will convert a pocketful of money into a mountain of wealth. The element of chance can be introduced in an application via an object of class Random (of namespace System). Objects of class Random can produce random byte, int and double values. In the next several examples, we use objects of class Random to produce random numbers.

A new random-number generator object can be created as follows:

Random randomNumbers = new Random();

The random-number generator object can then be used to generate random byte, int and double values—we discuss only random int values here.

Consider the following statement:

int randomValue = randomNumbers.Next();

Method Next of class Random generates a random int value in the range 0 to +2,147,483,646, inclusive. If the Next method truly produces values at random, then every value in that range should have an equal chance (or probability) of being chosen each time method Next is called. The values returned by Next are actually pseudorandom numbers—a sequence of values produced by a complex mathematical calculation. The calculation uses the current time of day (which, of course, changes constantly) to seed the random-number generator such that each execution of an application yields a different sequence of random values.

The range of values produced directly by method Next often differs from the range of values required in a particular C# application. For example, an application that simulates coin tossing might require only 0 for “heads” and 1 for “tails.” An application that simulates the rolling of a six-sided die might require random integers in the range 1–6. A video game that randomly predicts the next type of spaceship (out of four possibilities) that will fly across the horizon might require random integers in the range 1–4. For cases like these, class Random provides other versions of method Next. One receives an int argument and returns a value from 0 up to, but not including, the argument’s value. For example, you might use the statement

int randomValue = randomNumbers.Next( 6 );

which returns 0, 1, 2, 3, 4 or 5. The argument 6—called the scaling factor—represents the number of unique values that Next should produce (in this case, six—0, 1, 2, 3, 4 and 5). This manipulation is called scaling the range of values produced by Random method Next.

Suppose we wanted to simulate a six-sided die that has the numbers 1–6 on its faces, not 0–5. Scaling the range of values alone is not enough. So we shift the range of numbers produced. We could do this by adding a shifting value—in this case 1—to the result of method Next, as in

face = 1 + randomNumbers.Next( 6 );

The shifting value (1) specifies the first value in the desired set of random integers. The preceding statement assigns to face a random integer in the range 1–6.

The third alternative of method Next provides a more intuitive way to express both shifting and scaling. This method receives two int arguments and returns a value from the first argument’s value up to, but not including, the second argument’s value. We could use this method to write a statement equivalent to our previous statement, as in

face = randomNumbers.Next( 1, 7 );

Rolling a Six-Sided Die

To demonstrate random numbers, let’s develop an application that simulates 20 rolls of a six-sided die and displays each roll’s value. Figure 7.6 shows two sample outputs, which confirm that the results of the preceding calculation are integers in the range 1–6 and that each run of the application can produce a different sequence of random numbers. The using directive (line 3) enables us to use class Random without fully qualifying its name. Line 9 creates the Random object randomNumbers to produce random values. Line 16 executes 20 times in a loop to roll the die. The if statement (lines 21–22) starts a new line of output after every five numbers, so the results can be presented on multiple lines.

Example 7.6. Shifted and scaled random integers.

 1   // Fig. 7.6: RandomIntegers.cs
 2   // Shifted and scaled random integers.
 3   using System;
 4
 5   public class RandomIntegers
 6   {
 7      public static void Main( string[] args )
 8      {
 9         Random randomNumbers = new Random(); // random-number generator
10         int face; // stores each random integer generated
11
12         // loop 20 times
13         for ( int counter = 1; counter <= 20; counter++ )
14         {
15            // pick random integer from 1 to 6
16            face = randomNumbers.Next( 1, 7 );
17
18            Console.Write( "{0}  ", face ); // display generated value
19
20            // if counter is divisible by 5, start a new line of output
21            if ( counter % 5 == 0 )
22               Console.WriteLine();
23         } // end for
24      } // end Main
25   } // end class RandomIntegers
3  3  3  1  1
2  1  2  4  2
2  3  6  2  5
3  4  6  6  1
6  2  5  1  3
5  2  1  6  5
4  1  6  1  3
3  1  4  3  4

Rolling a Six-Sided Die 6000 Times

To show that the numbers produced by Next occur with approximately equal likelihood, let’s simulate 6000 rolls of a die (Fig. 7.7). Each integer from 1 to 6 should appear approximately 1000 times.

Example 7.7. Roll a six-sided die 6000 times.

 1   // Fig. 7.7: RollDie.cs
 2   // Roll a six-sided die 6000 times.
 3   using System;
 4
 5   public class RollDie
 6   {
 7      public static void Main( string[] args )
 8      {
 9         Random randomNumbers = new Random(); // random-number generator
10
11         int frequency1 = 0; // count of 1s rolled
12         int frequency2 = 0; // count of 2s rolled
13         int frequency3 = 0; // count of 3s rolled
14         int frequency4 = 0; // count of 4s rolled
15         int frequency5 = 0; // count of 5s rolled
16         int frequency6 = 0; // count of 6s rolled
17
18         int face; // stores most recently rolled value
19
20         // summarize results of 6000 rolls of a die
21         for ( int roll = 1; roll <= 6000; roll++ )
22         {
23            face = randomNumbers.Next( 1, 7 ); // number from 1 to 6
24
25            // determine roll value 1-6 and increment appropriate counter
26            switch ( face )
27            {
28               case 1:
29                  ++frequency1; // increment the 1s counter
30                  break;
31               case 2:
32                  ++frequency2; // increment the 2s counter
33                  break;
34               case 3:
35                  ++frequency3; // increment the 3s counter
36                  break;
37               case 4:
38                  ++frequency4; // increment the 4s counter
39                  break;
40               case 5:
41                  ++frequency5; // increment the 5s counter
42                  break;
43               case 6:
44                  ++frequency6; // increment the 6s counter
45                  break;
46            } // end switch
47         } // end for
48
49         Console.WriteLine( "Face	Frequency" ); // output headers
50         Console.WriteLine(
51            "1	{0}
2	{1}
3	{2}
4	{3}
5	{4}
6	{5}", frequency1,
52            frequency2, frequency3, frequency4, frequency5, frequency6 );
53      } // end Main
54   } // end class RollDie

Face      Frequency
1         1039
2         994
3         991
4         970
5         978
6         1028
Face      Frequency
1         985
2         985
3         1001
4         1017
5         1002
6         1010

As the two sample outputs show, the values produced by method Next enable the application to realistically simulate rolling a six-sided die. The application uses nested control statements (the switch is nested inside the for) to determine the number of times each side of the die occurred. The for statement (lines 21–47) iterates 6000 times. During each iteration, line 23 produces a random value from 1 to 6. This face value is then used as the switch expression (line 26) in the switch statement (lines 26–46). Based on the face value, the switch statement increments one of the six counter variables during each iteration of the loop. (In Section 8.4, we show an elegant way to replace the entire switch statement in this application with a single statement.) The switch statement has no default label because we have a case label for every possible die value that the expression in line 23 can produce. Run the application several times and observe the results. You’ll see that every time you execute this application, it produces different results.

Scaling and Shifting Random Numbers

Previously, we demonstrated the statement

face = randomNumbers.Next( 1, 7 );

which simulates the rolling of a six-sided die. This statement always assigns to variable face an integer in the range 1face < 7. The width of this range (i.e., the number of consecutive integers in the range) is 6, and the starting number in the range is 1. Referring to the preceding statement, we see that the width of the range is determined by the difference between the two integers passed to Random method Next, and the starting number of the range is the value of the first argument. We can generalize this result as

number = randomNumbers.Next( shiftingValue, shiftingValue + scalingFactor );

where shiftingValue specifies the first number in the desired range of consecutive integers and scalingFactor specifies how many numbers are in the range.

It’s also possible to choose integers at random from sets of values other than ranges of consecutive integers. For this purpose, it’s simpler to use the version of the Next method that takes only one argument. For example, to obtain a random value from the sequence 2, 5, 8, 11 and 14, you could use the statement

number = 2 + 3 * randomNumbers.Next( 5 );

In this case, randomNumberGenerator.Next(5) produces values in the range 0–4. Each value produced is multiplied by 3 to produce a number in the sequence 0, 3, 6, 9 and 12. We then add 2 to that value to shift the range of values and obtain a value from the sequence 2, 5, 8, 11 and 14. We can generalize this result as

number = shiftingValue +
   differenceBetweenValues * randomNumbers.Next( scalingFactor );

where shiftingValue specifies the first number in the desired range of values, difference-BetweenValues represents the difference between consecutive numbers in the sequence and scalingFactor specifies how many numbers are in the range.

Random-Number Repeatability for Testing and Debugging

As we mentioned earlier in Section 7.9, the methods of class Random actually generate pseudorandom numbers based on complex mathematical calculations. Repeatedly calling any of Random’s methods produces a sequence of numbers that appears to be random. The calculation that produces the pseudorandom numbers uses the time of day as a seed value to change the sequence’s starting point. Each new Random object seeds itself with a value based on the computer system’s clock at the time the object is created, enabling each execution of an application to produce a different sequence of random numbers.

When debugging an application, it’s sometimes useful to repeat the exact same sequence of pseudorandom numbers during each execution of the application. This repeatability enables you to prove that your application is working for a specific sequence of random numbers before you test the application with different sequences of random numbers. When repeatability is important, you can create a Random object as follows:

Random randomNumbers = new Random( seedValue );

The seedValue argument (type int) seeds the random-number calculation. If the same seedValue is used every time, the Random object produces the same sequence of random numbers.

Error-Prevention Tip 7.1

Error-Prevention Tip 7.1

While an application is under development, create the Random object with a specific seed value to produce a repeatable sequence of random numbers each time the application executes. If a logic error occurs, fix the error and test the application again with the same seed value—this allows you to reconstruct the same sequence of random numbers that caused the error. Once the logic errors have been removed, create the Random object without using a seed value, causing the Random object to generate a new sequence of random numbers each time the application executes.

Case Study: A Game of Chance (Introducing Enumerations)

One popular game of chance is the dice game known as “craps,” which is played in casinos and back alleys throughout the world. The rules of the game are straightforward:

You roll two dice. Each die has six faces, which contain one, two, three, four, five and six spots, respectively. After the dice have come to rest, the sum of the spots on the two upward faces is calculated. If the sum is 7 or 11 on the first throw, you win. If the sum is 2, 3 or 12 on the first throw (called “craps”), you lose (i.e., “the house” wins). If the sum is 4, 5, 6, 8, 9 or 10 on the first throw, that sum becomes your “point.” To win, you must continue rolling the dice until you “make your point” (i.e., roll that same point value). You lose by rolling a 7 before making your point.

The application in Fig. 7.8 simulates the game of craps, using methods to define the logic of the game. The Main method (lines 24–70) calls the static RollDice method (lines 73–85) as needed to roll the two dice and compute their sum. The four sample outputs show winning on the first roll, losing on the first roll, winning on a subsequent roll and losing on a subsequent roll, respectively. Variable randomNumbers (line 8) is declared static so it can be created once during the program’s execution and used in method RollDice.

Example 7.8. Craps class simulates the dice game craps.

 1   // Fig. 7.8: Craps.cs
 2   // Craps class simulates the dice game craps.
 3   using System;
 4
 5   public class Craps
 6   {
 7      // create random-number generator for use in method RollDice
 8      private static Random randomNumbers = new Random();
 9
10      // enumeration with constants that represent the game status
11      private enum Status { CONTINUE, WON, LOST }                 
12
13      // enumeration with constants that represent common rolls of the dice
14      private enum DiceNames
15      {                     
16         SNAKE_EYES = 2,     
17         TREY = 3,           
18         SEVEN = 7,          
19         YO_LEVEN = 11,      
20         BOX_CARS = 12       
21      }                      
22
23      // plays one game of craps
24      public static void Main( string[] args )
25      {
26         // gameStatus can contain CONTINUE, WON or LOST
27         Status gameStatus = Status.CONTINUE;
28         int myPoint = 0; // point if no win or loss on first roll
29
30         int sumOfDice = RollDice(); // first roll of the dice
31
32         // determine game status and point based on first roll
33         switch ( ( DiceNames ) sumOfDice )
34         {
35            case DiceNames.SEVEN: // win with 7 on first roll    
36            case DiceNames.YO_LEVEN: // win with 11 on first roll
37               gameStatus = Status.WON;                          
38               break;
39            case DiceNames.SNAKE_EYES: // lose with 2 on first roll
40            case DiceNames.TREY: // lose with 3 on first roll      
41            case DiceNames.BOX_CARS: // lose with 12 on first roll 
42               gameStatus = Status.LOST;                           
43               break;
44            default: // did not win or lose, so remember point  
45               gameStatus = Status.CONTINUE; // game is not over
46               myPoint = sumOfDice; // remember the point       
47               Console.WriteLine( "Point is {0}", myPoint );    
48               break;
49         } // end switch
50
51         // while game is not complete
52         while ( gameStatus == Status.CONTINUE ) // game not WON or LOST
53         {
54            sumOfDice = RollDice(); // roll dice again
55
56            // determine game status
57            if ( sumOfDice == myPoint ) // win by making point
58               gameStatus = Status.WON;
59            else
60               // lose by rolling 7 before point
61               if ( sumOfDice == ( int ) DiceNames.SEVEN )
62                  gameStatus = Status.LOST;
63         } // end while
64
65         // display won or lost message
66         if ( gameStatus == Status.WON )
67            Console.WriteLine( "Player wins" );
68         else
69            Console.WriteLine( "Player loses" );
70      } // end Main
71
72      // roll dice, calculate sum and display results
73      public static int RollDice()
74      {
75         // pick random die values
76         int die1 = randomNumbers.Next( 1, 7 ); // first die roll
77         int die2 = randomNumbers.Next( 1, 7 ); // second die roll
78
79         int sum = die1 + die2; // sum of die values
80
81         // display results of this roll
82         Console.WriteLine( "Player rolled {0} + {1} = {2}",
83            die1, die2, sum );
84         return sum; // return sum of dice
85      } // end method RollDice
86   } // end class Craps
Player rolled 2 + 5 = 7
Player wins
Player rolled 2 + 1 = 3
Player loses
Player rolled 4 + 6 = 10
Point is 10
Player rolled 1 + 3 = 4
Player rolled 1 + 3 = 4
Player rolled 2 + 3 = 5
Player rolled 4 + 4 = 8
Player rolled 6 + 6 = 12
Player rolled 4 + 4 = 8
Player rolled 4 + 5 = 9
Player rolled 2 + 6 = 8
Player rolled 6 + 6 = 12
Player rolled 6 + 4 = 10
Player wins
Player rolled 2 + 4 = 6
Point is 6
Player rolled 3 + 1 = 4
Player rolled 5 + 5 = 10
Player rolled 6 + 1 = 7
Player loses

Method RollDice

In the rules of the game, the player must roll two dice on the first roll and must do the same on all subsequent rolls. We declare method RollDice (lines 73–85) to roll the dice and compute and display their sum. Method RollDice is declared once, but it’s called from two places (lines 30 and 54) in method Main, which contains the logic for one complete game of craps. Method RollDice takes no arguments, so it has an empty parameter list. Each time it’s called, RollDice returns the sum of the dice, so the return type int is indicated in the method header (line 73). Although lines 76 and 77 look the same (except for the die names), they do not necessarily produce the same result. Each of these statements produces a random value in the range 1–6. Variable randomNumbers (used in lines 76 and 77) is not declared in the method. Rather it’s declared as a private static variable of the class and initialized in line 8. This enables us to create one Random object that’s reused in each call to RollDice.

Method Main’s Local Variables

The game is reasonably involved. The player may win or lose on the first roll or may win or lose on any subsequent roll. Method Main (lines 24–70) uses local variable gameStatus (line 27) to keep track of the overall game status, local variable myPoint (line 28) to store the “point” if the player does not win or lose on the first roll and local variable sumOfDice (line 30) to maintain the sum of the dice for the most recent roll. Variable myPoint is initialized to 0 to ensure that the application will compile. If you do not initialize myPoint, the compiler issues an error, because myPoint is not assigned a value in every branch of the switch statement—thus, the application could try to use myPoint before it’s definitely assigned a value. By contrast, gameStatus does not require initialization because it is assigned a value in every branch of the switch statement—thus, it’s guaranteed to be initialized before it’s used. However, as good programming practice, we initialize it anyway.

enum Type Status

Local variable gameStatus is declared to be of a new type called Status, which we declared in line 11. Type Status is declared as a private member of class Craps, because Status will be used only in that class. Status is a user-defined type called an enumeration, which declares a set of constants represented by identifiers. An enumeration is introduced by the keyword enum and a type name (in this case, Status). As with a class, braces ( { and }) delimit the body of an enum declaration. Inside the braces is a comma-separated list of enumeration constants. The enum constant names must be unique, but the value associated with each constant need not be.

Good Programming Practice 7.3

Good Programming Practice 7.3

Use only uppercase letters in the names of constants. This makes the constants stand out in an application and reminds you that enumeration constants are not variables.

Variables of type Status should be assigned only one of the three constants declared in the enumeration. When the game is won, the application sets local variable gameStatus to Status.WON (lines 37 and 58). When the game is lost, the application sets local variable gameStatus to Status.LOST (lines 42 and 62). Otherwise, the application sets local variable gameStatus to Status.CONTINUE (line 45) to indicate that the dice must be rolled again.

Good Programming Practice 7.4

Good Programming Practice 7.4

Using enumeration constants (like Status.WON, Status.LOST and Status.CONTINUE) rather than literal integer values (such as 0, 1 and 2) can make code easier to read and maintain.

Logic of the Main Method

Line 30 in method Main calls RollDice, which picks two random values from 1 to 6, displays the value of the first die, the value of the second die and the sum of the dice, and returns the sum of the dice. Method Main next enters the switch statement at lines 33–49, which uses the sumOfDice value from line 30 to determine whether the game has been won or lost, or whether it should continue with another roll.

The sums of the dice that would result in a win or loss on the first roll are declared in the DiceNames enumeration in lines 14–21. These are used in the cases of the switch statement. The identifier names use casino parlance for these sums. Notice that in the DiceNames enumeration, a value is explicitly assigned to each identifier name. When the enum is declared, each constant in the enum declaration is a constant value of type int. If you do not assign a value to an identifier in the enum declaration, the compiler will do so. If the first enum constant is unassigned, the compiler gives it the value 0. If any other enum constant is unassigned, the compiler gives it a value equal to one more than the value of the preceding enum constant. For example, in the Status enumeration, the compiler implicitly assigns 0 to Status.WON, 1 to Status.CONTINUE and 2 to Status.LOST.

You could also declare an enum’s underlying type to be byte, sbyte, short, ushort, int, uint, long or ulong by writing

private enum MyEnum : typeName { Constant1, Constant2, ... }

where typeName represents one of the integral simple types.

If you need to compare a simple integral type value to the underlying value of an enumeration constant, you must use a cast operator to make the two types match. In the switch statement at lines 33–49, we use the cast operator to convert the int value in sumOfDice to type DiceNames and compare it to each of the constants in DiceNames. Lines 35–36 determine whether the player won on the first roll with SEVEN (7) or YO_LEVEN (11). Lines 39–41 determine whether the player lost on the first roll with SNAKE_EYES (2), TREY (3) or BOX_CARS (12). After the first roll, if the game is not over, the default case (lines 44–48) saves sumOfDice in myPoint (line 46) and displays the point (line 47).

If we’re still trying to “make our point” (i.e., the game is continuing from a prior roll), the loop in lines 52–63 executes. Line 54 rolls the dice again. If sumOfDice matches myPoint in line 57, line 58 sets gameStatus to Status.WON, and the loop terminates because the game is complete. In line 61, we use the cast operator ( int ) to obtain the underlying value of DiceNames.SEVEN so that we can compare it to sumOfDice. If sumOfDice is equal to SEVEN (7), line 62 sets gameStatus to Status.LOST, and the loop terminates because the game is over. When the game completes, lines 66–69 display a message indicating whether the player won or lost, and the application terminates.

Summary of the Craps Example

Note the use of the various program-control mechanisms we’ve discussed. The Craps class uses two methods—Main and RollDice (called twice from Main)—and the switch, while, if...else and nested if control statements. Also, notice that we use multiple case labels in the switch statement to execute the same statements for sums of SEVEN and YO_LEVEN (lines 35–36) and for sums of SNAKE_EYES, TREY and BOX_CARS (lines 39–41). To easily create a switch statement with all possible values for an enum type, you can use the switch code snippet. Type switch in the C# code then press Tab twice. If you enter an enum type into the switch statement’s expression (the highlighted code of the snippet) and press Enter, a case for each enum constant will be generated automatically.

Scope of Declarations

You’ve seen declarations of C# entities, such as classes, methods, properties, variables and parameters. Declarations introduce names that can be used to refer to such C# entities. The scope of a declaration is the portion of the application that can refer to the declared entity by its unqualified name. Such an entity is said to be “in scope” for that portion of the application. This section introduces several important scope issues. The basic scope rules are as follows:

  1. The scope of a parameter declaration is the body of the method in which the declaration appears.

  2. The scope of a local-variable declaration is from the point at which the declaration appears to the end of the block containing the declaration.

  3. The scope of a local-variable declaration that appears in the initialization section of a for statement’s header is the body of the for statement and the other expressions in the header.

  4. The scope of a method, property or field of a class is the entire body of the class. This enables non-static methods and properties of a class to use any of the class’s fields, methods and properties, regardless of the order in which they’re declared. Similarly, static methods and properties can use any of the static members of the class.

Any block may contain variable declarations. If a local variable or parameter in a method has the same name as a field, the field is hidden until the block terminates. In Chapter 10, we discuss how to access hidden fields. The application in Fig. 7.9 demonstrates scoping issues with fields and local variables.

Example 7.9. Scope class demonstrates static and local variable scopes.

 1   // Fig. 7.9: Scope.cs
 2   // Scope class demonstrates static and local variable scopes.
 3   using System;
 4
 5   public class Scope
 6   {
 7      // static variable that is accessible to all methods of this class
 8      private static int x = 1;                                         
 9
10      // Main creates and initializes local variable x
11      // and calls methods UseLocalVariable and UseStaticVariable
12      public static void Main( string[] args )
13      {
14         int x = 5; // method's local variable x hides static variable x
15
16         Console.WriteLine( "local x in method Main is {0}", x );
17
18         // UseLocalVariable has its own local x
19         UseLocalVariable();
20
21         // UseStaticVariable uses class Scope's static variable x
22         UseStaticVariable();
23
24         // UseLocalVariable reinitializes its own local x
25         UseLocalVariable();
26
27         // class Scope's static variable x retains its value
28         UseStaticVariable();
29
30         Console.WriteLine( "
local x in method Main is {0}", x );
31      } // end Main
32
33      // create and initialize local variable x during each call
34      public static void UseLocalVariable()
35      {
36         int x = 25; // initialized each time UseLocalVariable is called
37
38         Console.WriteLine(
39            "
local x on entering method UseLocalVariable is {0}", x );
40         ++x; // modifies this method's local variable x
41         Console.WriteLine(
42            "local x before exiting method UseLocalVariable is {0}", x );
43      } // end method UseLocalVariable
44
45      // modify class Scope's static variable x during each call
46      public static void UseStaticVariable()
47      {
48         Console.WriteLine( "
static variable x on entering {0} is {1}",
49            "method UseStaticVariable", x );
50         x *= 10; // modifies class Scope's static variable x
51         Console.WriteLine( "static variable x before exiting {0} is {1}",
52            "method UseStaticVariable", x );
53      } // end method UseStaticVariable
54   } // end class Scope
local x in method Main is 5

local x on entering method UseLocalVariable is 25
local x before exiting method UseLocalVariable is 26

static variable x on entering method UseStaticVariable is 1
static variable x before exiting method UseStaticVariable is 10

local x on entering method UseLocalVariable is 25
local x before exiting method UseLocalVariable is 26

static variable x on entering method UseStaticVariable is 10
static variable x before exiting method UseStaticVariable is 100

local x in method Main is 5

Error-Prevention Tip 7.2

Error-Prevention Tip 7.2

Use different names for fields and local variables to help prevent subtle logic errors that occur when a method is called and a local variable of the method hides a field of the same name in the class.

Line 8 declares and initializes the static variable x to 1. This static variable is hidden in any block (or method) that declares local variable named x. Method Main (lines 12–31) declares local variable x (line 14) and initializes it to 5. This local variable’s value is output to show that static variable x (whose value is 1) is hidden in method Main. The application declares two other methods—UseLocalVariable (lines 34–43) and UseStaticVariable (lines 46–53)—that each take no arguments and do not return results. Method Main calls each method twice (lines 19–28). Method UseLocalVariable declares local variable x (line 36). When UseLocalVariable is first called (line 19), it creates local variable x and initializes it to 25 (line 36), outputs the value of x (lines 38–39), increments x (line 40) and outputs the value of x again (lines 41–42). When UseLocalVariable is called a second time (line 25), it re-creates local variable x and reinitializes it to 25, so the output of each UseLocalVariable call is identical.

Method UseStaticVariable does not declare any local variables. Therefore, when it refers to x, static variable x (line 8) of the class is used. When method UseStaticVariable is first called (line 22), it outputs the value (1) of static variable x (lines 48–49), multiplies the static variable x by 10 (line 50) and outputs the value (10) of static variable x again (lines 51–52) before returning. The next time method UseStaticVariable is called (line 28), the static variable has its modified value, 10, so the method outputs 10, then 100. Finally, in method Main, the application outputs the value of local variable x again (line 30) to show that none of the method calls modified Main’s local variable x, because the methods all referred to variables named x in other scopes.

Method Overloading

Methods of the same name can be declared in the same class, as long as they have different sets of parameters (determined by the number, types and order of the parameters). This is called method overloading. When an overloaded method is called, the C# compiler selects the appropriate method by examining the number, types and order of the arguments in the call. Method overloading is commonly used to create several methods with the same name that perform the same or similar tasks, but on different types or different numbers of arguments. For example, Math methods Min and Max (summarized in Section 7.3) are overloaded with 11 versions. These find the minimum and maximum, respectively, of two values of each of the 11 numeric simple types. Our next example demonstrates declaring and invoking overloaded methods. You’ll see examples of overloaded constructors in Chapter 10.

Declaring Overloaded Methods

In class MethodOverload (Fig. 7.10), we include two overloaded versions of a method called Square—one that calculates the square of an int (and returns an int) and one that calculates the square of a double (and returns a double). Although these methods have the same name and similar parameter lists and bodies, you can think of them simply as different methods. It may help to think of the method names as “Square of int” and “Square of double,” respectively.

Example 7.10. Overloaded method declarations.

 1   // Fig. 7.10: MethodOverload.cs
 2   // Overloaded method declarations.
 3   using System;
 4
 5   public class MethodOverload
 6   {
 7      // test overloaded square methods
 8      public static void Main( string[] args )
 9      {
10         Console.WriteLine( "Square of integer 7 is {0}", Square( 7 ) );
11         Console.WriteLine( "Square of double 7.5 is {0}", Square( 7.5 ) );
12      } // end Main
13
14      // square method with int argument                           
15      public static int Square( int intValue )                     
16      {                                                            
17         Console.WriteLine( "Called square with int argument: {0}",
18            intValue );                                            
19         return intValue * intValue;                               
20      } // end method Square with int argument                     
21
22      // square method with double argument                           
23      public static double Square( double doubleValue )               
24      {                                                               
25         Console.WriteLine( "Called square with double argument: {0}",
26            doubleValue );                                            
27         return doubleValue * doubleValue;                            
28      } // end method Square with double argument                     
29   } // end class MethodOverload
Called square with int argument: 7
Square of integer 7 is 49
Called square with double argument: 7.5
Square of double 7.5 is 56.25

Line 10 in Main invokes method Square with the argument 7. Literal integer values are treated as type int, so the method call in line 10 invokes the version of Square at lines 15–20 that specifies an int parameter. Similarly, line 11 invokes method Square with the argument 7.5. Literal real-number values are treated as type double, so the method call in line 11 invokes the version of Square at lines 23–28 that specifies a double parameter. Each method first outputs a line of text to prove that the proper method was called in each case.

Notice that the overloaded methods in Fig. 7.10 perform the same calculation, but with two different types. C#’s generics feature provides a mechanism for writing a single “generic method” that can perform the same tasks as an entire set of overloaded methods. We discuss generic methods in Chapter 22.

Distinguishing Between Overloaded Methods

The compiler distinguishes overloaded methods by their signature—a combination of the method’s name and the number, types and order of its parameters. The signature also includes the way those parameters are passed, which can be modified by the ref and out keywords (discussed in Section 7.16). If the compiler looked only at method names during compilation, the code in Fig. 7.10 would be ambiguous—the compiler would not know how to distinguish between the Square methods (lines 15–20 and 23–28). Internally, the compiler uses signatures to determine whether a class’s methods are unique in that class.

For example, in Fig. 7.10, the compiler will use the method signatures to distinguish between the “Square of int” method (the Square method that specifies an int parameter) and the “Square of double” method (the Square method that specifies a double parameter). If Method1’s declaration begins as

void Method1( int a, float b )

then that method will have a different signature than the method declared beginning with

void Method1( float a, int b )

The order of the parameter types is important—the compiler considers the preceding two Method1 headers to be distinct.

Return Types of Overloaded Methods

In discussing the logical names of methods used by the compiler, we did not mention the return types of the methods. This is because method calls cannot be distinguished by return type. The application in Fig. 7.11 illustrates the compiler errors generated when two methods have the same signature but different return types. Overloaded methods can have the same or different return types if the methods have different parameter lists. Also, overloaded methods need not have the same number of parameters.

Example 7.11. Overloaded methods with identical signatures cause compilation errors, even if return types are different.

 1   // Fig. 7.11: MethodOverload.cs
 2   // Overloaded methods with identical signatures
 3   // cause compilation errors, even if return types are different.
 4   public class MethodOverloadError
 5   {
 6      // declaration of method Square with int argument
 7      public int Square( int x )
 8      {
 9         return x * x;
10      } // end method Square
11
12      // second declaration of method Square with int argument
13      // causes compilation error even though return types are different
14      public double Square( int y )
15      {
16         return y * y;
17      } // end method Square
18   } // end class MethodOverloadError
Overloaded methods with identical signatures cause compilation errors, even if return types are different.

Common Programming Error 7.10

Common Programming Error 7.10

Declaring overloaded methods with identical parameter lists is a compilation error regardless of whether the return types are different.

Optional Parameters

As of Visual C# 2010, methods can have optional parameters that allow the calling method to vary the number of arguments to pass. An optional parameter specifies a default value that’s assigned to the parameter if the optional argument is omitted.

You can create methods with one or more optional parameters. All optional parameters must be placed to the right of the method’s non-optional parameters—that is, at the end of the parameter list.

Common Programming Error 7.11

Common Programming Error 7.11

Declaring a non-optional parameter to the right of an optional one is a compilation error.

When a parameter has a default value, the caller has the option of passing that particular argument. For example, the method header

public int Power( int baseValue, int exponentValue = 2 )

specifies an optional second parameter. Any call to Power must pass at least an argument for the parameter baseValue, or a compilation error occurs. Optionally, a second argument (for the exponentValue parameter) can be passed to Power. Consider the following calls to Power:

Power()
Power(10)
Power(10, 3)

The first call generates a compilation error because this method requires a minimum of one argument. The second call is valid because one argument (10) is being passed—the optional exponentValue is not specified in the method call. The last call is also valid—10 is passed as the required argument and 3 is passed as the optional argument.

In the call that passes only one argument (10), parameter exponentValue defaults to 2, which is the default value specified in the method’s header. Each optional parameter must specify a default value by using an equal (=) sign followed by the value. For example, the header for Power sets 2 as exponentValue’s default value.

Figure 7.12 demonstrates an optional parameter. The program calculates the result of raising a base value to an exponent. Method Power (Fig. 7.12, lines 15–23) specifies that its second parameter is optional. In method DisplayPowers, lines 10–11 of Fig. 7.12 call method Power. Line 10 calls the method without the optional second argument. In this case, the compiler provides the second argument, 2, using the default value of the optional argument, which is not visible to you in the call.

Example 7.12. Optional argument demonstration with method Power.

 1   // Fig. 7.12: Power.vb
 2   // Optional argument demonstration with method Power.
 3   using System;
 4
 5   class CalculatePowers
 6   {
 7      // call Power with and without optional arguments
 8      public static void Main( string[] args )
 9      {
10         Console.WriteLine( "Power(10) = {0}", Power( 10 ) ) ;
11         Console.WriteLine( "Power(2, 10) = {0}", Power( 2, 10 ) );
12      } // end Main
13
14      // use iteration to calculate power
15      public int Power( int baseValue, int exponentValue = 2 )
16      {
17         int result = 1; // initialize total
18
19         for ( int i = 1; i <= exponentValue; i++ )
20            result *= baseValue;
21
22         return result;
23      } // end method Power
24   } // end class CalculatePowers
Power(10) = 100
Power(2, 10) = 1024

Named Parameters

Normally, when calling a method that has optional parameters, the argument values—in order—are assigned to the parameters from left to right in the parameter list. Consider a Time class that stores the time of day in 24-hour clock format as int values representing the hour (0–23), minute (0–59) and second (0–59). Such a class might provide a SetTime method with optional parameters like

public void SetTime( int hour = 0, int minute = 0, int second = 0 )

In the preceding method header, all of three of SetTime’s parameters are optional. Assuming that we have a Time object named t, we can call SetTime as follows:

t.SetTime(); // sets the time to 12:00:00 AM
t.SetTime( 12 ); // sets the time to 12:00:00 PM
t.SetTime( 12, 30 ); // sets the time to 12:30:00 PM
t.SetTime( 12, 30, 22 ); // sets the time to 12:30:22 PM

In the first call, no arguments are specified, so the compiler assigns 0 to each parameter. In the second call, the compiler assigns the argument, 12, to the first parameter, hour, and assigns default values of 0 to the minute and second parameters. In the third call, the compiler assigns the two arguments, 12 and 30, to the parameters hour and minute, respectively, and assigns the default value 0 to the parameter second. In the last call, the compiler assigns the three arguments, 12, 30 and 22, to the parameters hour, minute and second, respectively.

What if you wanted to specify only arguments for the hour and second? You might think that you could call the method as follows:

t.SetTime( 12, , 22 ); // COMPILATION ERROR

Unlike some programming languages, C# doesn’t allow you to skip an argument as shown in the preceding statement. However, Visual C# 2010 provides a new feature called named parameters, which enable you to call methods that receive optional parameters by providing only the optional arguments you wish to specify. To do so, you explicitly specify the parameter’s name and value—separated by a colon (:)—in the argument list of the method call. For example, the preceding statement can be implemented in Visual C# 2010 as follows:

t.SetTime( hour: 12, second: 22 ); // sets the time to 12:00:22

In this case, the compiler assigns parameter hour the argument 12 and parameter second the argument 22. The parameter minute is not specified, so the compiler assigns it the default value 0. It’s also possible to specify the arguments out of order when using named parameters. The arguments for the required parameters must always be supplied.

Recursion

The applications we’ve discussed thus far are generally structured as methods that call one another in a disciplined, hierarchical manner. For some problems, however, it’s useful to have a method call itself. A recursive method is a method that calls itself, either directly or indirectly through another method.

We consider recursion conceptually first. Then we examine an application containing a recursive method. Recursive problem-solving approaches have a number of elements in common. When a recursive method is called to solve a problem, it actually is capable of solving only the simplest case(s), or base case(s). If the method is called with a base case, it returns a result. If the method is called with a more complex problem, it divides the problem into two conceptual pieces: a piece that the method knows how to do and a piece that it does not know how to do. To make recursion feasible, the latter piece must resemble the original problem, but be a slightly simpler or slightly smaller version of it. Because this new problem looks like the original problem, the method calls a fresh copy of itself to work on the smaller problem; this is referred to as a recursive call and is also called the recursion step. The recursion step normally includes a return statement, because its result will be combined with the portion of the problem the method knew how to solve to form a result that will be passed back to the original caller.

The recursion step executes while the original call to the method is still active (i.e., while it has not finished executing). The recursion step can result in many more recursive calls, as the method divides each new subproblem into two conceptual pieces. For the recursion to terminate eventually, each time the method calls itself with a slightly simpler version of the original problem, the sequence of smaller and smaller problems must converge on the base case. At that point, the method recognizes the base case and returns a result to the previous copy of the method. A sequence of returns ensues until the original method call returns the result to the caller. This process sounds complex compared with the conventional problem solving we’ve performed to this point.

Recursive Factorial Calculations

As an example of recursion concepts at work, let’s write a recursive application to perform a popular mathematical calculation. Consider the factorial of a nonnegative integer n, written n! (and pronounced “n factorial”), which is the product

n · (n – 1) · (n – 2) · ... · 1

1! is equal to 1 and 0! is defined to be 1. For example, 5! is the product 5 · 4 · 3 · 2 · 1, which is equal to 120.

The factorial of an integer, number, greater than or equal to 0 can be calculated iteratively (nonrecursively) using the for statement as follows:

factorial = 1;
for ( int counter = number; counter >= 1; counter-- )
   factorial *= counter;

A recursive declaration of the factorial method is arrived at by observing the following relationship:

n! = n · (n – 1)!

For example, 5! is clearly equal to 5 · 4!, as is shown by the following equations:

5! = 5 · 4 · 3 · 2 · 1
5! = 5 · (4 · 3 · 2 · 1)
5! = 5 · (4!)

The evaluation of 5! would proceed as shown in Fig. 7.13. Figure 7.13(a) shows how the succession of recursive calls proceeds until 1! is evaluated to be 1, which terminates the recursion. Figure 7.13(b) shows the values returned from each recursive call to its caller until the value is calculated and returned.

Recursive evaluation of 5!.

Figure 7.13. Recursive evaluation of 5!.

Figure 7.14 uses recursion to calculate and display the factorials of the integers from 0 to 10. The recursive method Factorial (lines 16–24) first tests to determine whether a terminating condition (line 19) is true. If number is less than or equal to 1 (the base case), Factorial returns 1, no further recursion is necessary and the method returns. If number is greater than 1, line 23 expresses the problem as the product of number and a recursive call to Factorial evaluating the factorial of number - 1, which is a slightly simpler problem than the original calculation, Factorial( number ).

Example 7.14. Recursive Factorial method.

 1   // Fig. 7.14: FactorialTest.cs
 2   // Recursive Factorial method.
 3   using System;
 4
 5   public class FactorialTest
 6   {
 7      public static void Main( string[] args )
 8      {
 9         // calculate the factorials of 0 through 10
10         for ( long counter = 0; counter <= 10; counter++ )
11            Console.WriteLine( "{0}! = {1}",
12               counter, Factorial( counter ) );
13      } // end Main
14
15      // recursive declaration of method Factorial
16      public static long Factorial( long number )
17      {
18         // base case
19         if ( number <= 1 )
20            return 1;
21         // recursion step
22         else
23            return number * Factorial( number - 1 );
24      } // end method Factorial
25   } // end class FactorialTest
0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800

Method Factorial (lines 16–24) receives a parameter of type long and returns a result of type long. As you can see in Fig. 7.14, factorial values become large quickly. We chose type long (which can represent relatively large integers) so that the application could calculate factorials greater than 20!. Unfortunately, the Factorial method produces large values so quickly that factorial values soon exceed even the maximum value that can be stored in a long variable. Due to the restrictions on the integral types, variables of type float, double or decimal might ultimately be needed to calculate factorials of larger numbers. This situation points to a weakness in many programming languages—the languages are not easily extended to handle the unique requirements of various applications. As you know, C# allows you to create a type that supports arbitrarily large integers if you wish. For example, you could create a HugeInteger class (which we ask you to do in Exercise 10.10) that would enable an application to calculate the factorials of arbitrarily large numbers. You can also use the new type BigInteger from the .NET Framework’s class library.

Common Programming Error 7.12

Common Programming Error 7.12

Either omitting the base case or writing the recursion step incorrectly so that it does not converge on the base case will cause infinite recursion, eventually exhausting memory. This error is analogous to the problem of an infinite loop in an iterative (nonrecursive) solution.

Passing Arguments: Pass-by-Value vs. Pass-by-Reference

Two ways to pass arguments to functions in many programming languages are pass-by-value and pass-by-reference. When an argument is passed by value (the default in C#), a copy of its value is made and passed to the called function. Changes to the copy do not affect the original variable’s value in the caller. This prevents the accidental side effects that so greatly hinder the development of correct and reliable software systems. Each argument that has been passed in the programs in this chapter so far has been passed by value. When an argument is passed by reference, the caller gives the method the ability to access and modify the caller’s original variable.

Software Engineering Observation 7.4

Software Engineering Observation 7.4

Pass-by-reference can weaken security, because the called function can corrupt the caller’s data.

To pass an object by reference into a method, simply provide as an argument in the method call the variable that refers to the object. Then, in the method body, reference the object using the parameter name. The parameter refers to the original object in memory, so the called method can access the original object directly.

Previously, we discussed the difference between value types and reference types. A major difference between them is that value-type variables store values, so specifying a value-type variable in a method call passes a copy of that variable’s value to the method. Reference-type variables store references to objects, so specifying a reference-type variable as an argument passes the method a copy of the actual reference that refers to the object. Even though the reference itself is passed by value, the method can still use the reference it receives to interact with—and possibly modify—the original object. Similarly, when returning information from a method via a return statement, the method returns a copy of the value stored in a value-type variable or a copy of the reference stored in a reference-type variable. When a reference is returned, the calling method can use that reference to interact with the referenced object.

ref and out Parameters

What if you would like to pass a variable by reference so the called method can modify the variable’s value? To do this, C# provides keywords ref and out. Applying the ref keyword to a parameter declaration allows you to pass a variable to a method by reference—the called method will be able to modify the original variable in the caller. The ref keyword is used for variables that already have been initialized in the calling method. Normally, when a method call contains an uninitialized variable as an argument, the compiler generates an error. Preceding a parameter with keyword out creates an output parameter. This indicates to the compiler that the argument will be passed into the called method by reference and that the called method will assign a value to the original variable in the caller. If the method does not assign a value to the output parameter in every possible path of execution, the compiler generates an error. This also prevents the compiler from generating an error message for an uninitialized variable that’s passed as an argument to a method. A method can return only one value to its caller via a return statement, but can return many values by specifying multiple output (ref and/or out) parameters.

You can also pass a reference-type variable by reference, which allows you to modify reference-type variable so that it refers to a new object. Passing a reference by reference is a tricky but powerful technique that we discuss in Section 8.8.

Demonstrating ref, out and Value Parameters

The application in Fig. 7.15 uses the ref and out keywords to manipulate integer values. The class contains three methods that calculate the square of an integer. Method SquareRef (lines 37–40) multiplies its parameter x by itself and assigns the new value to x. SquareRef’s parameter is declared as ref int, which indicates that the argument passed to this method must be an integer that’s passed by reference. Because the argument is passed by reference, the assignment at line 39 modifies the original argument’s value in the caller.

Example 7.15. Reference, output and value parameters.

 1   // Fig. 7.15: ReferenceAndOutputParameters.cs
 2   // Reference, output and value parameters.
 3   using System;
 4
 5   class ReferenceAndOutputParameters
 6   {
 7      // call methods with reference, output and value parameters
 8      public static void Main( string[] args )
 9      {
10         int y = 5; // initialize y to 5
11         int z; // declares z, but does not initialize it
12
13         // display original values of y and z
14         Console.WriteLine( "Original value of y: {0}", y );
15         Console.WriteLine( "Original value of z: uninitialized
" );
16
17         // pass y and z by reference               
18         SquareRef( ref y ); // must use keyword ref
19         SquareOut( out z ); // must use keyword out
20
21         // display values of y and z after they are modified by
22         // methods SquareRef and SquareOut, respectively
23         Console.WriteLine( "Value of y after SquareRef: {0}", y );
24         Console.WriteLine( "Value of z after SquareOut: {0}
", z );
25
26         // pass y and z by value
27         Square( y );
28         Square( z );
29
30         // display values of y and z after they are passed to method Square
31         // to demonstrate that arguments passed by value are not modified
32         Console.WriteLine( "Value of y after Square: {0}", y );
33         Console.WriteLine( "Value of z after Square: {0}", z );
34      } // end Main
35
36      // uses reference parameter x to modify caller's variable
37      static void SquareRef( ref int x )
38      {
39         x = x * x; // squares value of caller's variable
40      } // end method SquareRef
41
42      // uses output parameter x to assign a value
43      // to an uninitialized variable
44      static void SquareOut( out int x )
45      {
46         x = 6; // assigns a value to caller's variable
47         x = x * x; // squares value of caller's variable
48      } // end method SquareOut
49
50      // parameter x receives a copy of the value passed as an argument,
51      // so this method cannot modify the caller's variable
52      static void Square( int x )
53      {
54         x = x * x;
55      } // end method Square
56   } // end class ReferenceAndOutputParameters
Original value of y: 5
Original value of z: uninitialized

Value of y after SquareRef: 25
Value of z after SquareOut: 36

Value of y after Square: 25
Value of z after Square: 36

Method SquareOut (lines 44–48) assigns its parameter the value 6 (line 46), then squares that value. SquareOut’s parameter is declared as out int, which indicates that the argument passed to this method must be an integer that’s passed by reference and that the argument does not need to be initialized in advance.

Method Square (lines 52–55) multiplies its parameter x by itself and assigns the new value to x. When this method is called, a copy of the argument is passed to the parameter x. Thus, even though parameter x is modified in the method, the original value in the caller is not modified.

Method Main (lines 8–34) invokes methods SquareRef, SquareOut and Square. We begin by initializing variable y to 5 and declaring, but not initializing, variable z. Lines 18–19 call methods SquareRef and SquareOut. Notice that when you pass a variable to a method with a reference parameter, you must precede the argument with the same keyword (ref or out) that was used to declare the reference parameter. Lines 23–24 display the values of y and z after the calls to SquareRef and SquareOut. Notice that y has been changed to 25 and z has been set to 36.

Lines 27–28 call method Square with y and z as arguments. In this case, both variables are passed by value—only copies of their values are passed to Square. As a result, the values of y and z remain 25 and 36, respectively. Lines 32–33 output the values of y and z to show that they were not modified.

Common Programming Error 7.13

Common Programming Error 7.13

The ref and out arguments in a method call must match the parameters specified in the method declaration; otherwise, a compilation error occurs.

Software Engineering Observation 7.5

Software Engineering Observation 7.5

By default, C# does not allow you to choose whether to pass each argument by value or by reference. Value types are passed by value. Objects are not passed to methods; rather, references to objects are passed to methods. The references themselves are passed by value. When a method receives a reference to an object, the method can manipulate the object directly, but the reference value cannot be changed to refer to a new object. In Section 8.8, you’ll see that references also can be passed by reference.

Wrap-Up

In this chapter, we discussed the difference between non-static and static methods, and we showed how to call static methods by preceding the method name with the name of the class in which it appears and the member access (.) operator. You saw that the Math class in the .NET Framework Class Library provides many static methods to perform mathematical calculations. We presented several commonly used Framework Class Library namespaces. You learned how to use operator + to perform string concatenations. You also learned how to declare constant values in two ways—with the const keyword and with enum types. We demonstrated simulation techniques and used class Random to generate sets of random numbers. We discussed the scope of fields and local variables in a class. You saw how to overload methods in a class by providing methods with the same name but different signatures. You learned how to use optional and named parameters. We discussed how recursive methods call themselves, breaking larger problems into smaller subproblems until eventually the original problem is solved. You learned the differences between value types and reference types with respect to how they’re passed to methods, and how to use the ref and out keywords to pass arguments by reference.

In Chapter 8, you’ll learn how to maintain lists and tables of data in arrays. You’ll see a more elegant implementation of the application that rolls a die 6000 times and two enhanced versions of our GradeBook case study. You’ll also learn how to access an application’s command-line arguments that are passed to method Main when a console application begins execution.

Summary

Section 7.1 Introduction

  • Experience has shown that the best way to develop and maintain a large application is to construct it from small, simple pieces. This technique is called divide and conquer.

Section 7.2 Packaging Code in C#

  • Three common ways of packaging code are methods, classes and namespaces.

  • Methods allow you to modularize an application by separating its tasks into self-contained units.

  • Dividing an application into meaningful methods makes it easier to debug and maintain.

Section 7.3 static Methods, static Variables and Class Math

  • You can call any static method by specifying the name of the class in which it’s declared, followed by the member access (.) operator and the method name, as in

    ClassName. MethodName( arguments )
  • Method arguments may be constants, variables or expressions.

  • A constant is declared with the keyword const—its value cannot be changed after the constant is declared.

  • Class Math declares constants Math.PI and Math.E. Math.PI (3.14159265358979323846) is the ratio of a circle’s circumference to its diameter. Math.E (2.7182818284590452354) is the base value for natural logarithms.

  • When each object of a class maintains its own copy of an attribute, each object (instance) of the class has a separate instance of the variable. When objects of a class containing static variables are created, all objects of that class share one copy of the class’s static variables.

  • Together the static variables and instance variables represent the fields of a class.

  • When you execute your application, you can specify a list of strings (separated by spaces) as command-line arguments. The execution environment will pass these arguments to the Main method of your application.

  • If you declare more than one Main method among all the classes of your project, you’ll need to indicate which one you would like to be the application’s entry point. You can do this by clicking the menu Project > [ProjectName] Properties... and selecting the class containing the Main method that should be the entry point from the Startup object list box.

Section 7.4 Declaring Methods with Multiple Parameters

  • Multiple parameters are specified as a comma-separated list.

  • When a method is called, each parameter is initialized with the value of the corresponding argument. There must be one argument in the method call for each required parameter in the method declaration. Each argument must be consistent with the type of the corresponding parameter.

  • When program control returns to the point in the application where a method was called, the method’s parameters are no longer accessible.

  • Methods can return at most one value; the returned value can be a value type that contains many values (implemented as a struct) or a reference to an object that contains many values.

  • C# allows string objects to be created by assembling smaller strings into larger strings using operator +. This is known as string concatenation.

  • Every value of a simple type in C# has a string representation. When one of the + operator’s operands is a string, the other is implicitly converted to a string, then the two are concatenated.

  • All objects have a ToString method that returns a string representation of the object. When an object is concatenated with a string, the object’s ToString method is implicitly called to obtain the string representation of the object.

Section 7.5 Notes on Declaring and Using Methods

  • You’ve seen three ways to call a method—using a method name by itself to call another method of the same class; using a variable that contains a reference to an object, followed by the member access (.) operator and the method name to call a non-static method of the referenced object; and using the class name and the member access (.) operator to call a static method of a class.

  • A static method can call only other static methods of the same class directly and can manipulate only static variables in the same class directly.

  • There are three ways to return control to the statement that calls a method. If the method does not return a result, control returns when the program flow reaches the method-ending right brace or when the statement

    • return;

    is executed. If the method returns a result, the statement

    • return expression;

    evaluates the expression, then returns the result to the caller.

Section 7.6 Method-Call Stack and Activation Records

  • Stacks are known as last-in, first-out (LIFO) data structures—the last item pushed (inserted) on the stack is the first item popped off (removed from) the stack.

  • When an application calls a method, the called method must know how to return to its caller, so the return address of the calling method is pushed onto the program-execution stack. If a series of method calls occurs, the successive return addresses are pushed onto the stack in last-in, first-out order so that each method can return to its caller.

  • The program-execution stack also contains the memory for the local variables used in each invocation of a method during an application’s execution. This data, stored as a portion of the program-execution stack, is known as the activation record or stack frame of the method call.

  • If a local variable holding a reference to an object is the only variable in the application with a reference to that object, when the activation record containing that local variable is popped off the stack, the object will eventually be deleted from memory during “garbage collection.”

  • The amount of memory in a computer is finite. If more method calls occur than can have their activation records stored on the program-execution stack, stack overflow occurs.

Section 7.7 Argument Promotion and Casting

  • Another important feature of method calls is argument promotion—implicitly converting an argument’s value to the type that the method expects to receive in its corresponding parameter.

  • The argument-promotion rules apply to expressions containing values of two or more simple types and to simple-type values passed as arguments to methods.

  • In cases where information may be lost due to conversion between simple types, the compiler requires you to use a cast operator to explicitly force the conversion.

Section 7.8 The .NET Framework Class Library

  • Many predefined classes are grouped into categories of related classes called namespaces. Together, these namespaces are referred to as the .NET Framework Class Library.

Section 7.9 Case Study: Random-Number Generation

  • Random method Next generates a random int value in the range 0 to +2,147,483,646, inclusive.

  • Class Random provides other versions of method Next. One receives an int and returns a value from 0 up to, but not including, the argument’s value. The other receives two ints and returns a value from the first argument’s value up to, but not including, the second argument’s value.

  • The methods of class Random actually generate pseudorandom numbers based on complex mathematical calculations. The calculation that produces the pseudorandom numbers uses the time of day as a seed value to change the sequence’s starting point.

  • If the same seed value is used every time, the Random object produces the same sequence of random numbers.

Section 7.10 Case Study: A Game of Chance (Introducing Enumerations)

  • An enumeration is introduced by the keyword enum and a type name. Braces delimit the body of an enum declaration. Inside the braces is a comma-separated list of enumeration constants.

  • Variables of an enum type should be assigned only constants of that enum type.

  • When an enum is declared, each constant in the enum declaration is a constant value of type int. If you do not assign a value to an identifier in the enum declaration, the compiler will do so. If the first enum constant is unassigned, the compiler gives it the value 0. If any other enum constant is unassigned, the compiler gives it a value equal to one more than the value of the preceding enum constant. The enum constant names must be unique, but their underlying values need not be.

  • If you need to compare a simple integral type value to the underlying value of an enumeration constant, you must use a cast operator to make the two types match.

Section 7.11 Scope of Declarations

  • The scope of a declaration is the portion of the application that can refer to the declared entity by its unqualified name.

  • The scope of a parameter declaration is the body of the method in which the declaration appears.

  • A local variable’s scope is from the point at which the declaration appears to the end of that block.

  • The scope of a local-variable declaration that appears in the initialization section of a for statement’s header is the body of the for statement and the other expressions in the header.

  • The scope of a method, property or field of a class is the entire body of the class.

  • Any block may contain variable declarations. If a local variable or parameter in a method has the same name as a field, the field is hidden until the block terminates execution.

Section 7.12 Method Overloading

  • Methods of the same name can be declared in the same class, as long as they have different sets of parameters. This is called method overloading. When an overloaded method is called, the C# compiler selects the appropriate method by examining the number, types and order of the arguments in the call.

  • The compiler distinguishes overloaded methods by their signature—a combination of the method’s name and the number, types and order of its parameters. The signature also includes the way those parameters are passed, which can be modified by the ref and out keywords.

  • The compiler will generate an error when two methods have the same signature but different return types. Overloaded methods can have the same or different return types if the methods have different parameter lists.

Section 7.13 Optional Parameters

  • As of Visual C# 2010, methods can have optional parameters, which allow the calling method to vary the number of arguments to pass. An optional parameter specifies a default value that’s assigned to the parameter if the optional argument is omitted.

  • Methods can have one or more optional parameters. All optional parameters must be placed to the right of the method’s non-optional parameters.

  • When a parameter has a default value, the caller has the option of passing that particular argument.

Section 7.14 Named Parameters

  • Normally, when calling a method that has optional parameters, the argument values—in order—are assigned to the parameters from left to right in the parameter list.

  • Visual C# 2010 provides a new feature called named parameters, which enable you to call methods that receive optional parameters by providing only the optional arguments you wish to specify. To do so, you explicitly specify the parameter’s name and value—separated by a colon (:)—in the argument list of the method call.

Section 7.15 Recursion

  • A recursive method calls itself, either directly or indirectly through another method.

  • When a recursive method is called to solve a problem, the method actually is capable of solving only the simplest case(s), or base case(s). If the method is called with a base case, the method returns a result.

  • If the method is called with a more complex problem, the method divides the problem into two conceptual pieces: a piece that the method knows how to do and a piece that it does not know how to do. Because this new problem looks like the original problem, the method calls a fresh copy of itself to work on the smaller problem; this procedure is referred to as a recursive call and is also called the recursion step.

  • A recursive declaration of the factorial method is arrived at by observing the relationship:

    • n! = n · ( n – 1)!

Section 7.16 Passing Arguments: Pass-by-Value vs. Pass-by-Reference

  • Two ways to pass arguments to functions in many programming languages are pass-by-value and pass-by-reference.

  • When an argument is passed by value (the default), a copy of the argument’s value is passed to the called function. Changes to the copy do not affect the original variable’s value in the caller.

  • When an argument is passed by reference, the caller gives the method the ability to access and modify the caller’s original data directly.

  • Value-type variables store values, so specifying a value-type variable in a method call passes a copy of that variable’s value to the method. Reference-type variables store references to objects, so specifying a reference-type variable as an argument passes the method a copy of the actual reference that refers to the object.

  • When returning information from a method via a return statement, the method returns a copy of the value stored in a value-type variable or a copy of the reference stored in a reference-type variable.

  • C# provides the keywords ref and out to pass variables by reference.

  • A ref parameter indicates that an argument will be passed to the method by reference—the called method will be able to modify the original variable in the caller.

  • An out parameter indicates that a possibly uninitialized variable will be passed into the method by reference and that the called method will assign a value to the original variable in the caller.

  • A method can return only one value to its caller via a return statement, but can return many values by specifying multiple output (ref and/or out) parameters.

  • When a variable is passed to a method with a reference parameter, you must precede the variable with the same keyword (ref or out) that was used to declare the reference parameter.

Self-Review Exercises

7.1

Fill in the blanks in each of the following statements:

  1. A method is invoked with a(n) ________.

  2. A variable known only within the method in which it’s declared is called a(n) ______.

  3. The ________ statement in a called method can be used to pass the value of an expression back to the calling method.

  4. The keyword ________ indicates that a method does not return a value.

  5. Data can be added to or removed from only the ________ of a stack.

  6. Stacks are known ________ as data structures—the last item pushed (inserted) on the stack is the first item popped off (removed from) the stack.

  7. The three ways to return control from a called method to a caller are ________, ________ and _______.

  8. An object of class _______ produces pseudorandom numbers.

  9. The program-execution stack contains the memory for local variables on each invocation of a method during an application’s execution. This data, stored as a portion of the program-execution stack, is known as the _______ or _______ of the method call.

  10. If there are more method calls than can be stored on the program execution stack, an error known as a(n) ________ occurs.

  11. The _________ of a declaration is the portion of an application that can refer to the entity in the declaration by its unqualified name.

  12. It’s possible to have several methods with the same name that each operate on different types or numbers of arguments. This feature is called method _________.

  13. The program-execution stack is also referred to as the ________ stack.

  14. A method that calls itself either directly or indirectly is a(n) ________ method.

  15. A recursive method typically has two components: one that provides a means for the recursion to terminate by testing for a(n) ________ case and one that expresses the problem as a recursive call for a slightly simpler problem than does the original call.

7.1

  1. method call.

  2. local variable.

  3. return.

  4. void.

  5. top.

  6. last-in-first-out (LIFO).

  7. return; or return expression; or encountering the closing right brace of a method.

  8. Random.

  9. activation record, stack frame.

  10. stack overflow.

  11. scope.

  12. overloading.

  13. method call.

  14. recursive.

  15. base.

7.2

For the class Craps in Fig. 7.8, state the scope of each of the following entities:

  1. the variable randomNumbers.

  2. the variable die1.

  3. the method RollDice.

  4. the method Main.

  5. the variable sumOfDice.

7.2

  1. class body.

  2. block that defines method RollDice’s body.

  3. class body.

  4. class body.

  5. block that defines method Main’s body.

7.3

Write an application that tests whether the examples of the Math class method calls shown in Fig. 7.2 actually produce the indicated results.

7.3

The following solution demonstrates the Math class methods in Fig. 7.2:

 1   // Exercise 7.3 Solution: MathTest.cs
 2   // Testing the Math class methods.
 3   using System;
 4
 5   public class MathTest
 6   {
 7      public static void Main( string[] args )
 8      {
 9         Console.WriteLine( "Math.Abs( 23.7 ) = {0}", Math.Abs( 23.7 ) );
10         Console.WriteLine( "Math.Abs( 0.0 ) = {0}", Math.Abs( 0.0 ) );
11         Console.WriteLine( "Math.Abs( -23.7 ) = {0}", Math.Abs( -23.7 ) );
12         Console.WriteLine( "Math.Ceiling( 9.2 ) = {0}",
13            Math.Ceiling( 9.2 ) );
14         Console.WriteLine( "Math.Ceiling( -9.8 ) = {0}",
15            Math.Ceiling( -9.8 ) );
16         Console.WriteLine( "Math.Cos( 0.0 ) = {0}", Math.Cos( 0.0 ) );
17         Console.WriteLine( "Math.Exp( 1.0 ) = {0}", Math.Exp( 1.0 ) );
18         Console.WriteLine( "Math.Exp( 2.0 ) = {0}", Math.Exp( 2.0 ) );
19         Console.WriteLine( "Math.Floor( 9.2 ) = {0}", Math.Floor( 9.2 ) );
20         Console.WriteLine( "Math.Floor( -9.8 ) = {0}",
21            Math.Floor( -9.8 ) );
22         Console.WriteLine( "Math.Log( Math.E ) = {0}",
23            Math.Log( Math.E ) );
24         Console.WriteLine( "Math.Log( Math.E * Math.E ) = {0}",
25            Math.Log( Math.E * Math.E ) );
26         Console.WriteLine( "Math.Max( 2.3, 12.7 ) = {0}",
27            Math.Max( 2.3, 12.7 ) );
28         Console.WriteLine( "Math.Max( -2.3, -12.7 ) = {0}",
29            Math.Max( -2.3, -12.7 ) );
30         Console.WriteLine( "Math.Min( 2.3, 12.7 ) = {0}",
31            Math.Min( 2.3, 12.7 ) );
32         Console.WriteLine( "Math.Min( -2.3, -12.7 ) = {0}",
33            Math.Min( -2.3, -12.7 ) );
34         Console.WriteLine( "Math.Pow( 2.0, 7.0 ) = {0}",
35            Math.Pow( 2.0, 7.0 ) );
36         Console.WriteLine( "Math.Pow( 9.0, 0.5 ) = {0}",
37            Math.Pow( 9.0, 0.5 ) );
38         Console.WriteLine( "Math.Sin( 0.0 ) = {0}", Math.Sin( 0.0 ) );
39         Console.WriteLine( "Math.Sqrt( 900.0 ) = {0}",
40            Math.Sqrt( 900.0 ) );
41         Console.WriteLine( "Math.Tan( 0.0 ) = {0}", Math.Tan( 0.0 ) );
42      } // end Main
43   } // end class MathTest
Math.Abs( 23.7 ) = 23.7
Math.Abs( 0.0 ) = 0
Math.Abs( -23.7 ) = 23.7
Math.Ceiling( 9.2 ) = 10
Math.Ceiling( -9.8 ) = -9
Math.Cos( 0.0 ) = 1
Math.Exp( 1.0 ) = 2.71828182845905
Math.Exp( 2.0 ) = 7.38905609893065
Math.Floor( 9.2 ) = 9
Math.Floor( -9.8 ) = -10
Math.Log( Math.E ) = 1
Math.Log( Math.E * Math.E ) = 2
Math.Max( 2.3, 12.7 ) = 12.7
Math.Max( -2.3, -12.7 ) = -2.3
Math.Min( 2.3, 12.7 ) = 2.3
Math.Min( -2.3, -12.7 ) = -12.7
Math.Pow( 2.0, 7.0 ) = 128
Math.Pow( 9.0, 0.5 ) = 3
Math.Sin( 0.0 ) = 0
Math.Sqrt( 900.0 ) = 30
Math.Tan( 0.0 ) = 0

7.4

Give the method header for each of the following methods:

  1. Method Hypotenuse, which takes two double-precision, floating-point arguments side1 and side2 and returns a double-precision, floating-point result.

  2. Method Smallest, which takes three integers x, y and z and returns an integer.

  3. Method Instructions, which does not take any arguments and does not return a value. [Note: Such methods are commonly used to display instructions to a user.]

  4. Method IntToDouble, which takes integer argument number and returns a double value.

7.4

  1. double Hypotenuse( double side1, double side2)

  2. int Smallest( int x, int y, int z)

  3. void Instructions()

  4. double IntToDouble( int number)

7.5

Find the error in each of the following code segments. Explain how to correct the error.

  1. void G()
    {
       Console.WriteLine( "Inside method G" );
       void H()
       {
          Console.WriteLine( "Inside method H" );
       }
    }
  2. int Sum( int x, int y )
    {
       int result;
       result = x + y;
    }
  3. void F( float a );
    {
       float a;
       Console.WriteLine( a );
    }
  4. void Product()
    {
       int a = 6, b = 5, c = 4, result;
       result = a * b * c;
       Console.WriteLine( "Result is " + result );
       return result;
    }

7.5

  1. Error: Method H is declared within method G.

    Correction: Move the declaration of H outside the declaration of G.

  2. Error: The method is supposed to return an integer, but does not.

    Correction: Delete variable result and place the statement

    return x + y;

    in the method, or add the following statement at the end of the method body:

    return result;
  3. Error: The semicolon after the right parenthesis of the parameter list is incorrect, and the parameter a should not be redeclared in the method.

    Correction: Delete the semicolon after the right parenthesis of the parameter list, and delete the declaration float a;.

  4. Error: The method returns a value when it’s not supposed to.

    Correction: Change the return type from void to int.

7.6

Write a complete C# application to prompt the user for the double radius of a sphere, and call method SphereVolume to calculate and display the volume of the sphere. Use the following statement to calculate the volume:

double volume = ( 4.0 / 3.0 ) * Math.PI * Math.Pow( radius, 3 )

7.6

The following solution calculates the volume of a sphere, using the radius entered by the user:

 1   // Exercise 7.6 Solution: Sphere.cs
 2   // Calculate the volume of a sphere.
 3   using System;
 4
 5   public class Sphere
 6   {
 7      // obtain radius from user and display volume of sphere
 8      public static void Main( string[] args )
 9      {
10         Console.Write( "Enter radius of sphere: " );
11         double radius = Convert.ToDouble( Console.ReadLine() );
12
13         Console.WriteLine( "Volume is {0:F3}", SphereVolume( radius ) );
14      } // end Main
15
16      // calculate and return sphere volume
17      public static double SphereVolume( double radius )
18      {
19         double volume = ( 4.0 / 3.0 ) * Math.PI * Math.Pow( radius, 3 );
20         return volume;
21      } // end method SphereVolume
22   } // end class Sphere
Enter radius of sphere: 4
Volume is 268.083

Answers to Self-Review Exercises

Exercises

7.7

What is the value of x after each of the following statements is executed?

  1. x = Math.Abs( 7.5 );

  2. x = Math.Floor( 7.5 );

  3. x = Math.Abs( 0.0 );

  4. x = Math.Ceiling( 0.0 );

  5. x = Math.Abs( -6.4 );

  6. x = Math.Ceiling( -6.4 );

  7. x = Math.Ceiling( -Math.Abs( -8 + Math.Floor( -5.5 ) ) );

7.8

(Parking Charges) A parking garage charges a $2.00 minimum fee to park for up to three hours. The garage charges an additional $0.50 per hour for each hour or part thereof in excess of three hours. The maximum charge for any given 24-hour period is $10.00. Assume that no car parks for longer than 24 hours at a time. Write an application that calculates and displays the parking charges for each customer who parked in the garage yesterday. You should enter the hours parked for each customer. The application should display the charge for the current customer and should calculate and display the running total of yesterday’s receipts. The application should use method CalculateCharges to determine the charge for each customer.

7.9

(Rounding to Nearest Integer) An application of method Math.Floor is rounding a value to the nearest integer. The statement

y = Math.Floor( x + 0.5 );

will round the number x to the nearest integer and assign the result to y. Write an application that reads double values and uses the preceding statement to round each of the numbers to the nearest integer. For each number processed, display both the original number and the rounded number.

7.10

(Rounding to a Specific Decimal Place) Math.Floor may be used to round a number to a specific decimal place. The statement

y = Math.Floor( x * 10 + 0.5 ) / 10;

rounds x to the tenths position (i.e., the first position to the right of the decimal point). The statement

y = Math.Floor( x * 100 + 0.5 ) / 100;

rounds x to the hundredths position (i.e., the second position to the right of the decimal point). Write an application that defines four methods for rounding a number x in various ways:

  1. RoundToInteger( number )

  2. RoundToTenths( number )

  3. RoundToHundredths( number )

  4. RoundToThousandths( number )

For each value read, your application should display the original value, the number rounded to the nearest integer, the number rounded to the nearest tenth, the number rounded to the nearest hundredth and the number rounded to the nearest thousandth.

7.11

Answer each of the following questions:

  1. What does it mean to choose numbers “at random”?

  2. Why is the Random class useful for simulating games of chance?

  3. Why is it often necessary to scale or shift the values produced by a Random object?

  4. Why is computerized simulation of real-world situations a useful technique?

7.12

Write statements that assign random integers to the variable n in the following ranges. Assume Random randomNumbers = new Random() has been defined and use the two-parameter version of the method Random.Next.

  1. 1 ≤n ≤2

  2. 1 ≤n ≤100

  3. 0 ≤n ≤9

  4. 1000 ≤n ≤1112

  5. –1 ≤n ≤1

  6. –3 ≤n ≤11

7.13

For each of the following sets of integers, write a single statement that will display a number at random from the set. Assume Random randomNumbers = new Random() has been defined and use the one-parameter version of method Random.Next.

  1. 2, 4, 6, 8, 10.

  2. 3, 5, 7, 9, 11.

  3. 6, 10, 14, 18, 22.

7.14

(Exponentiation) Write a method IntegerPower( base, exponent ) that returns the value of baseexponent

For example, IntegerPower( 3, 4 ) calculates 34 (or 3 * 3 * 3 * 3). Assume that exponent is a positive integer and that base is an integer. Method IntegerPower should use a for or while loop to control the calculation. Do not use any Math-library methods. Incorporate this method into an application that reads integer values for base and exponent and performs the calculation with the IntegerPower method.

7.15

(Hypotenuse of a Right Triangle) Write method Hypotenuse that calculates the length of the hypotenuse of a right triangle when the lengths of the other two sides are given. The method should take two arguments of type double and return the hypotenuse as a double. Incorporate this method into an application that reads values for side1 and side2 and performs the calculation with the Hypotenuse method. Determine the length of the hypotenuse for each of the triangles in Fig. 7.16.

Table 7.16. Values for the sides of triangles in Exercise 7.15.

Triangle

Side 1

Side 2

1

3.0

4.0

2

5.0

12.0

3

8.0

15.0

7.16

(Multiples) Write method Multiple that determines, for a pair of integers, whether the second integer is a multiple of the first. The method should take two integer arguments and return true if the second is a multiple of the first and false otherwise. Incorporate this method into an application that inputs a series of pairs of integers (one pair at a time) and determines whether the second value in each pair is a multiple of the first.

7.17

(Even or Odd) Write method IsEven that uses the remainder operator (%) to determine whether an integer is even. The method should take an integer argument and return true if the integer is even and false otherwise. Incorporate this method into an application that inputs a sequence of integers (one at a time) and determines whether each is even or odd.

7.18

(Displaying a Square of Asterisks) Write method SquareOfAsterisks that displays a solid square (the same number of rows and columns) of asterisks whose side length is specified in integer parameter side. For example, if side is 4, the method should display

****
****
****
****

Incorporate this method into an application that reads an integer value for side from the user and outputs the asterisks with the SquareOfAsterisks method.

7.19

(Displaying a Square of Any Character) Modify the method created in Exercise 7.18 to form the square out of whatever character is contained in character parameter FillCharacter. Thus, if side is 5 and FillCharacter is “#,” the method should display

#####
#####
#####
#####
#####

[Hint: Use the expression Convert.ToChar(Console.Read()) to read a character from the user.]

7.20

(Circle Area) Write an application that prompts the user for the radius of a circle and uses method CircleArea to calculate the area of the circle.

7.21

(Separating Digits) Write code segments that accomplish each of the following tasks:

  1. Calculate the integer part of the quotient when integer a is divided by integer b.

  2. Calculate the integer remainder when integer a is divided by integer b.

  3. Use the application pieces developed in parts (a) and (b) to write a method DisplayDigits that receives an integer between 1 and 99999 and displays it as a sequence of digits, separating each pair of digits by two spaces. For example, the integer 4562 should appear as:

    4  5  6  2.
  4. Incorporate the method developed in part (c) into an application that inputs an integer and calls DisplayDigits by passing the method the integer entered. Display the results.

7.22

(Temperature Conversions) Implement the following integer methods:

  1. Method Celsius returns the Celsius equivalent of a Fahrenheit temperature, using the calculation

    c = 5.0 / 9.0 * ( f - 32 );
  2. Method Fahrenheit returns the Fahrenheit equivalent of a Celsius temperature, using the calculation

    f = 9.0 / 5.0 * c + 32;
  3. Use the methods from parts (a) and (b) to write an application that enables the user either to enter a Fahrenheit temperature and display the Celsius equivalent or to enter a Celsius temperature and display the Fahrenheit equivalent.

7.23

(Find the Minimum) Write a method Minimum3 that returns the smallest of three floating-point numbers. Use the Math.Min method to implement Minimum3. Incorporate the method into an application that reads three values from the user, determines the smallest value and displays the result.

7.24

(Perfect Numbers) An integer number is said to be a perfect number if its factors, including 1 (but not the number itself), sum to the number. For example, 6 is a perfect number, because 6 = 1 + 2 + 3. Write method Perfect that determines whether parameter value is a perfect number. Use this method in an application that determines and displays all the perfect numbers between 2 and 1000. Display the factors of each perfect number to confirm that the number is indeed perfect.

7.25

(Prime Numbers) An integer is said to be prime if it’s greater than 1 and divisible by only 1 and itself. For example, 2, 3, 5 and 7 are prime, but 4, 6, 8 and 9 are not.

  1. Write a method that determines whether a number is prime.

  2. Use this method in an application that displays all the prime numbers less than 10,000.

  3. Initially, you might think that n/2 is the upper limit for which you must test to see whether a number is prime, but you need only go as high as the square root of n. Rewrite the application, and run it both ways.

7.26

(Reversing Digits) Write a method that takes an integer value and returns the number with its digits reversed. For example, given the number 7631, the method should return 1367. Incorporate the method into an application that reads a value from the user and displays the result.

7.27

(Greatest Common Divisor) The greatest common divisor (GCD) of two integers is the largest integer that evenly divides each of the two numbers. Write method Gcd that returns the greatest common divisor of two integers. Incorporate the method into an application that reads two values from the user and displays the result.

7.28

(Converting Grade Averages to a Four-Point Scale) Write method QualityPoints that inputs a student’s average and returns 4 if the student’s average is 90–100, 3 if the average is 80–89, 2 if the average is 70–79, 1 if the average is 60–69 and 0 if the average is lower than 60. Incorporate the method into an application that reads a value from the user and displays the result.

7.29

(Coin Tossing) Write an application that simulates coin tossing. Let the application toss a coin each time the user chooses the “Toss Coin” menu option. Count the number of times each side of the coin appears. Display the results. The application should call a separate method Flip that takes no arguments and returns false for tails and true for heads. [Note: If the application realistically simulates coin tossing, each side of the coin should appear approximately half the time.]

7.30

(Guess the Number Game) Write an application that plays “guess the number” as follows: Your application chooses the number to be guessed by selecting a random integer in the range 1 to 1000. The application displays the prompt Guess a number between 1 and 1000. The player inputs a first guess. If the player’s guess is incorrect, your application should display Too high. Try again. or Too low. Try again. to help the player “zero in” on the correct answer. The application should prompt the user for the next guess. When the user enters the correct answer, display Congratulations. You guessed the number! and allow the user to choose whether to play again. [Note: The guessing technique employed in this problem is similar to a binary search, which is discussed in Chapter 20.]

7.31

(Enhanced Guess the Number Game) Modify the application of Exercise 7.30 to count the number of guesses the player makes. If the number is 10 or fewer, display Either you know the secret or you got lucky! If the player guesses the number in 10 tries, display Aha! You know the secret! If the player makes more than 10 guesses, display You should be able to do better! Why should it take no more than 10 guesses? Well, with each “good guess,” the player should be able to eliminate half of the numbers. Now show why any number from 1 to 1000 can be guessed in 10 or fewer tries.

7.32

(Distance Between Two Points) Write method Distance to calculate the distance between two points (x1, y1) and (x2, y2). All numbers and return values should be of type double. Incorporate this method into an application that enables the user to enter the coordinates of the points.

7.33

(Craps Game Modification) Modify the craps application of Fig. 7.8 to allow wagering. Initialize variable balance to 1000 dollars. Prompt the player to enter a wager. Check that wager is less than or equal to balance, and if it’s not, have the user reenter wager until a valid wager is entered. After a correct wager is entered, run one game of craps. If the player wins, increase balance by wager and display the new balance. If the player loses, decrease balance by wager, display the new balance, check whether balance has become zero and, if so, display the message "Sorry. You busted!"

7.34

(Binary, Octal and Hexadecimal) Write an application that displays a table of the binary, octal, and hexadecimal equivalents of the decimal numbers in the range 1–256. If you’re not familiar with these number systems, read Appendix D first.

7.35

(Recursive Power Calculation) Write recursive method Power(base, exponent) that, when called, returns

base exponent

For example, Power(3, 4) = 3 * 3 * 3 * 3. Assume that exponent is an integer greater than or equal to 1. The recursion step should use the relationship

base exponent = base · base exponent – 1

The terminating condition occurs when exponent is equal to 1, because

base1 = base

Incorporate this method into an application that enables the user to enter the base and exponent.

7.36

(Towers of Hanoi) Every budding computer scientist must grapple with certain classic problems, and the Towers of Hanoi (see Fig. 7.17) is one of the most famous. Legend has it that in a temple in the Far East, priests are attempting to move a stack of disks from one peg to another. The initial stack has 64 disks threaded onto one peg and arranged from bottom to top by decreasing size. The priests are attempting to move the stack from this peg to a second peg under the constraints that exactly one disk is moved at a time and at no time may a larger disk be placed above a smaller disk. A third peg is available for temporarily holding disks. Supposedly, the world will end when the priests complete their task, so there’s little incentive for us to facilitate their efforts.

The Towers of Hanoi for the case with four disks.

Figure 7.17. The Towers of Hanoi for the case with four disks.

Let’s assume that the priests are attempting to move the disks from peg 1 to peg 3. We wish to develop an algorithm that will display the precise sequence of peg-to-peg disk transfers.

If we were to approach this problem with conventional methods, we would rapidly find ourselves hopelessly knotted up in managing the disks. Instead, if we attack the problem with recursion in mind, it immediately becomes tractable. Moving n disks can be viewed in terms of moving only n − 1 disks (hence the recursion) as follows:

  1. Move n − 1 disks from peg 1 to peg 2, using peg 3 as a temporary holding area.

  2. Move the last disk (the largest) from peg 1 to peg 3.

  3. Move the n − 1 disks from peg 2 to peg 3, using peg 1 as a temporary holding area.

The process ends when the last task involves moving n = 1 disk (i.e., the base case). This task is accomplished by simply moving the disk, without the need for a temporary holding area.

Write an application to solve the Towers of Hanoi problem. Allow the user to enter the number of disks. Use a recursive Tower method with four parameters:

  1. the number of disks to be moved,

  2. the peg on which these disks are initially threaded,

  3. the peg to which this stack of disks is to be moved, and

  4. the peg to be used as a temporary holding area.

Your application should display the precise instructions it will take to move the disks from the starting peg to the destination peg. For example, to move a stack of three disks from peg 1 to peg 3, your application should display the following series of moves:

  • 1 --> 3 (This notation means “Move one disk from peg 1 to peg 3.”)

  • 1 --> 2

  • 3 --> 2

  • 1 --> 3

  • 2 --> 1

  • 2 --> 3

  • 1 --> 3

7.37

(What Does This Code Do?) What does the following method do?

// Parameter b must be a positive to prevent infinite recursion
public static int Mystery( int a, int b )
{
   if ( b == 1 )
      return a;
   else
      return a + Mystery( a, b - 1 );
}

7.38

(Find the Error) Find the error in the following recursive method, and explain how to correct it:

public static int Sum( int n )
{
   if ( n == 0 )
      return 0;
   else
      return n + Sum( n );
}

Making a Difference Exercises

As computer costs decline, it becomes feasible for every student, regardless of economic circumstance, to have a computer and use it in school. This creates exciting possibilities for improving the educational experience of all students worldwide as suggested by the next two exercises. [Note: Check out initiatives such as the One Laptop Per Child Project (www.laptop.org). Also, research “green” laptops—and note the key “going green” characteristics of these devices. Look into the Electronic Product Environmental Assessment Tool (www.epeat.net) which can help you assess the “greenness” of desktops, notebooks and monitors to help you decide which products to purchase.]

7.39

(Computer-Assisted Instruction) The use of computers in education is referred to as computer-assisted instruction (CAI). Write a program that will help an elementary school student learn multiplication. Use a Random object to produce two positive one-digit integers. The program should then prompt the user with a question, such as

How much is 6 times 7?

The student then inputs the answer. Next, the program checks the student’s answer. If it’s correct, display the message "Very good!" and ask another multiplication question. If the answer is wrong, display the message "No. Please try again." and let the student try the same question repeatedly until the student gets it right. A separate function should be used to generate each new question. This function should be called once when the application begins execution and each time the user answers the question correctly.

7.40

(Computer-Assisted Instruction: Reducing Student Fatigue) One problem in CAI environments is student fatigue. This can be reduced by varying the computer’s responses to hold the student’s attention. Modify the program of Exercise 7.39 so that various comments are displayed for each answer. Possible responses to a correct answer:

Very good!
Excellent!
Nice work!
Keep up the good work!

Possible responses to an incorrect answer:

No. Please try again.
Wrong. Try once more.
Don't give up!
No. Keep trying.

Use random-number generation to choose a number from 1 to 4 that will be used to select one of the four appropriate responses to each correct or incorrect answer. Use a switch statement to issue the responses.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.158.165