8. Arrays

Objectives

In this chapter you’ll learn:

• To use arrays to store data in and retrieve data from lists and tables of values.

• To declare arrays, initialize arrays and refer to individual elements of arrays.

• To use the foreach statement to iterate through arrays.

• To use implicitly typed local variables.

• To pass arrays to methods.

• To declare and manipulate multidimensional arrays.

Begin at the beginning, ... and go on till you come to the end: then stop.

Lewis Carroll

Now go, write it before them in a table, and note it in a book.

Isaiah 30:8

To go beyond is as wrong as to fall short.

Confucius

Outline

8.1 Introduction

8.2 Arrays

8.3 Declaring and Creating Arrays

8.4 Examples Using Arrays

8.5 Case Study: Card Shuffling and Dealing Simulation

8.6 foreach Statement

8.7 Passing Arrays and Array Elements to Methods

8.8 Passing Arrays by Value and by Reference

8.9 Case Study: Class GradeBook Using an Array to Store Grades

8.10 Multidimensional Arrays

8.11 Case Study: Class GradeBook Using a Rectangular Array

8.12 Variable-Length Argument Lists

8.13 Using Command-Line Arguments

8.14 Wrap-Up

8.1 Introduction

This chapter introduces the important topic of data structures—collections of related data items. Arrays are data structures consisting of related data items of the same type. Arrays are fixed-length entities—they remain the same length once they’re created, although an array variable may be reassigned such that it refers to a new array of a different length.

After discussing how arrays are declared, created and initialized, we present a series of examples that demonstrate several common array manipulations. We use arrays to simulate shuffling and dealing playing cards. The chapter demonstrates C#’s last structured control statement—the foreach repetition statement—which provides a concise notation for accessing data in arrays (and other data structures, as you’ll see in Chapter 9 and later in the book). We enhance the GradeBook case study using arrays to enable the class to store a set of grades and analyze student grades from multiple exams.

8.2 Arrays

An array is a group of variables (called elements) containing values that all have the same type. Recall that types are divided into two categories—value types and reference types. Arrays are reference types. As you’ll see, what we typically think of as an array is actually a reference to an array object. The elements of an array can be either value types or reference types, including other arrays. To refer to a particular element in an array, we specify the name of the reference to the array and the position number of the element in the array, which is known as the element’s index.

Figure 8.1 shows a logical representation of an integer array called c. This array contains 12 elements. An application refers to any one of these elements with an array-access expression that includes the name of the array, followed by the index of the particular element in square brackets ([]). The first element in every array has index zero and is sometimes called the zeroth element. Thus, the elements of array c are c[0], c[1], c[2] and so on. The highest index in array c is 11, which is one less than the number of elements in the array, because indices begin at 0. Array names follow the same conventions as other variable names.

Fig. 8.1. A 12-element array.

image

An index must be a nonnegative integer and can be an expression. For example, if we assume that variable a is 5 and variable b is 6, then the statement

c[ a + b ] += 2;

adds 2 to array element c[11]. An indexed array name is an array-access expression. Such expressions can be used on the left side of an assignment to place a new value into an array element. The array index must be a value of type int, uint, long or ulong, or a value of a type that can be implicitly promoted to one of these types.

Let’s examine array c in Fig. 8.1 more closely. The name of the variable that references the array is c. Every array instance knows its own length and provides access to this information with the Length property. For example, the expression c.Length uses array c’s Length property to determine the length of the array (that is, 12). The Length property of an array cannot be changed, because it does not provide a set accessor. The array’s 12 elements are referred to as c[0], c[1], c[2], ..., c[11]. Referring to elements outside of this range, such as c[-1] or c[12] is a runtime error. The value of c[0] is -45, the value of c[1] is 6, the value of c[2] is 0, the value of c[7] is 62 and the value of c[11] is 78. To calculate the sum of the values contained in the first three elements of array c and store the result in variable sum, we would write

sum = c[ 0 ] + c[ 1 ] + c[ 2 ];

To divide the value of c[6] by 2 and assign the result to the variable x, we would write

x = c[ 6 ] / 2;

8.3 Declaring and Creating Arrays

Arrays occupy space in memory. Since they’re objects, they’re typically created with keyword new. To create an array object, you specify the type and the number of array elements as part of an array-creation expression that uses keyword new. Such an expression returns a reference that can be stored in an array variable. The following declaration and array-creation expression create an array object containing 12 int elements and store the array’s reference in variable c:

int[] c = new int[ 12 ];

This expression can be used to create the array shown in Fig. 8.1 (but not the initial values in the array—we’ll show how to initialize the elements of an array momentarily). This task also can be performed as follows:

int[] c; // declare the array variable
c = new int[ 12 ]; // create the array; assign to array variable

In the declaration, the square brackets following the type int indicate that c is a variable that will refer to an array of ints (i.e., c will store a reference to an array object). In the assignment statement, the array variable c receives the reference to a new array object of 12 int elements. The number of elements can also be specified as an expression that’s calculated at execution time. When an array is created, each element of the array receives a default value—0 for the numeric simple-type elements, false for bool elements and null for references. As we’ll soon see, we can provide specific, nondefault initial element values when we create an array.

Common Programming Error 8.1

image

In the declaration of a variable that will refer to an array, specifying the number of elements in the square brackets (e.g., int[12] c;) is a syntax error.

An application can create several arrays in a single declaration. The following statement reserves 100 elements for string array b and 27 elements for string array x:

string[] b = new string[ 100 ], x = new string[ 27 ];

In this statement, string[] applies to each variable. For readability and ease of commenting, we prefer to split the preceding statement into two statements, as in:

string[] b = new string[ 100 ]; // create string array b
string[] x = new string[ 27 ]; // create string array x

An application can declare variables that will refer to arrays of value-type elements or reference-type elements. For example, every element of an int array is an int value, and every element of a string array is a reference to a string object.

Resizing an Array

Though arrays are fixed-length entities, you can use the static Array method Resize, which takes two arguments—the array to be resized and the new length—to create a new array with the specified length. This method copies the contents of the old array into the new array and sets the variable it receives as its first argument to reference the new array. For example, consider the following statements:

int[] newArray = new int[ 5 ];
Array.Resize( ref newArray, 10 );

The variable newArray initially refers to a five-element array. The resize method sets newArray to refer to a new 10-element array. If the new array is smaller than the old array, any content that cannot fit into the new array is truncated without warning.

8.4 Examples Using Arrays

This section presents several examples that demonstrate declaring arrays, creating arrays, initializing arrays and manipulating array elements.

Creating and Initializing an Array

The application of Fig. 8.2 uses keyword new to create an array of five int elements that are initially 0 (the default for int variables).

Fig. 8.2. Creating an array.

image

Line 9 declares array—a variable capable of referring to an array of int elements. Line 12 creates the five-element array object and assigns its reference to variable array. Line 14 outputs the column headings. The first column contains the index (09) of each array element, and the second column contains the default value (0) of each array element and has a field width of 8.

The for statement in lines 17–18 outputs the index number (represented by counter) and the value (represented by array[counter]) of each array element. The loop-control variable counter is initially 0—index values start at 0, so using zero-based counting allows the loop to access every element of the array. The for statement’s loop-continuation condition uses the property array.Length (line 17) to obtain the length of the array. In this example, the length of the array is 10, so the loop continues executing as long as the value of control variable counter is less than 10. The highest index value of a 10-element array is 9, so using the less-than operator in the loop-continuation condition guarantees that the loop does not attempt to access an element beyond the end of the array (i.e., during the final iteration of the loop, counter is 9). We’ll soon see what happens when such an out-of-range index is encountered at execution time.

Using an Array Initializer

An application can create an array and initialize its elements with an array initializer, which is a comma-separated list of expressions (called an initializer list) enclosed in braces. In this case, the array length is determined by the number of elements in the initializer list. For example, the declaration

int[] n = { 10, 20, 30, 40, 50 };

creates a five-element array with index values 0, 1, 2, 3 and 4. Element n[0] is initialized to 10, n[1] is initialized to 20 and so on. This statement does not require new to create the array object. When the compiler encounters an array initializer list, the compiler counts the number of initializers in the list to determine the size of the array, then sets up the appropriate new operation “behind the scenes.” The application in Fig. 8.3 initializes an integer array with 10 values (line 10) and displays the array in tabular format. The code for displaying the array elements (lines 15–16) is identical to that in Fig. 8.2 (lines 17–18).

Fig. 8.3. Initializing the elements of an array with an array initializer.

image

Calculating a Value to Store in Each Array Element

Some applications calculate the value to be stored in each array element. The application in Fig. 8.4 creates a 10-element array and assigns to each element one of the even integers from 2 to 20 (2, 4, 6, ..., 20). Then the application displays the array in tabular format. The for statement at lines 13–14 calculates an array element’s value by multiplying the current value of the for loop’s control variable counter by 2, then adding 2.

Fig. 8.4. Calculating values to be placed into the elements of an array.

image

Line 9 uses the modifier const to declare the constant ARRAY_LENGTH, whose value is 10. Constants must be initialized when they’re declared and cannot be modified thereafter. We declare constants with all capital letters by convention to make them stand out in the code.

Good Programming Practice 8.1

image

Constants also are called named constants. Applications using constants often are more readable than those that use literal values (e.g., 10)—a named constant such as ARRAY_LENGTH clearly indicates its purpose, whereas a literal value could have different meanings based on the context in which it’s used. Another advantage to using named constants is that if the value of the constant must be changed, the change is necessary only in the declaration, thus reducing the cost of maintaining the code.

Common Programming Error 8.2

image

Assigning a value to a named constant after it has been initialized is a compilation error.

Common Programming Error 8.3

image

Attempting to declare a named constant without initializing it is a compilation error.

Summing the Elements of an Array

Often, the elements of an array represent a series of values to be used in a calculation. For example, if the elements of an array represent exam grades, an instructor may wish to total the elements and use that total to calculate the class average for the exam. The GradeBook examples later in the chapter (Fig. 8.15 and Fig. 8.20) use this technique.

The application in Fig. 8.5 sums the values contained in a 10-element integer array. The application creates and initializes the array at line 9. The for statement performs the calculations. [Note: The values supplied as array initializers are often read into an application, rather than specified in an initializer list. For example, an application could input the values from a user or from a file on disk (as discussed in Chapter 17, Files and Streams). Reading the data into an application makes the application more reusable, because it can be used with different sets of data.]

Fig. 8.5. Computing the sum of the elements of an array.

image

Using Bar Charts to Display Array Data Graphically

Many applications present data to users in a graphical manner. For example, numeric values are often displayed as bars in a bar chart. In such a chart, longer bars represent proportionally larger numeric values. One simple way to display numeric data graphically is with a bar chart that shows each numeric value as a bar of asterisks (*).

An instructor might graph the number of grades in each of several categories to visualize the grade distribution for the exam. Suppose the grades on an exam were 87, 68, 94, 100, 83, 78, 85, 91, 76 and 87. There was one grade of 100, two grades in the 90s, four grades in the 80s, two grades in the 70s, one grade in the 60s and no grades below 60. Our next application (Fig. 8.6) stores this grade distribution data in an array of 11 elements, each corresponding to a category of grades. For example, array[0] indicates the number of grades in the range 0–9, array[7] the number of grades in the range 70–79 and array[10] the number of 100 grades. The two versions of class GradeBook later in the chapter (Figs. 8.15 and 8.20) contain code that calculates these grade frequencies based on a set of grades. For now, we manually create array by examining the set of grades and initializing the elements of array to the number of values in each range (line 9).

Fig. 8.6. Bar chart displaying application.

image

image

The application reads the numbers from the array and graphs the information as a bar chart. Each grade range is followed by a bar of asterisks indicating the number of grades in that range. To label each bar, lines 17–21 output a grade range (e.g., "70-79: ") based on the current value of counter. When counter is 10, line 18 outputs " 100: " to align the colon with the other bar labels. When counter is not 10, line 20 uses the format items {0:D2} and {1:D2} to output the label of the grade range. The format specifier D indicates that the value should be formatted as an integer, and the number after the D indicates how many digits this formatted integer must contain. The 2 indicates that values with fewer than two digits should begin with a leading 0.

The nested for statement (lines 24–25) outputs the bars. Note the loop-continuation condition at line 24 (stars < array[counter]). Each time the application reaches the inner for, the loop counts from 0 up to one less than array[counter], thus using a value in array to determine the number of asterisks to display. In this example, array[0]array[5] contain 0s because no students received a grade below 60. Thus, the application displays no asterisks next to the first six grade ranges.

Using the Elements of an Array as Counters

Sometimes, applications use counter variables to summarize data, such as the results of a survey. In Fig. 7.6, we used separate counters in our die-rolling application to track the number of times each face of a six-sided die appeared as the application rolled the die 6000 times. An array version of the application in Fig. 7.6 is shown in Fig. 8.7.

Fig. 8.7. Roll a six-sided die 6000 times.

image

Figure 8.7 uses array frequency (line 10) to count the occurrences of each side of the die. The single statement in line 14 of this application replaces lines 26–46 of Fig. 7.6. Line 14 uses the random value to determine which frequency element to increment during each iteration of the loop. The calculation in line 14 produces random numbers from 1 to 6, so array frequency must be large enough to store six counters. We use a seven-element array in which we ignore frequency[0]—it’s more logical to have the face value 1 increment frequency[1] than frequency[0]. Thus, each face value is used as an index for array frequency. We also replaced lines 50–52 of Fig. 7.6 by looping through array frequency to output the results (Fig. 8.7, lines 19–20).

Using Arrays to Analyze Survey Results

Our next example uses arrays to summarize data collected in a survey:

Forty students were asked to rate the quality of the food in the student cafeteria on a scale of 1 to 10 (where 1 means awful and 10 means excellent). Place the 40 responses in an integer array and summarize the results of the poll.

This is a typical array-processing application (see Fig. 8.8). We wish to summarize the number of responses of each type (i.e., 1 through 10). The array responses (lines 10–12) is a 40-element int array of the students’ responses to the survey. We use 11-element array frequency (line 13) to count the number of occurrences of each response. Each element of the array is used as a counter for one of the survey responses and is initialized to 0 by default. As in Fig. 8.7, we ignore frequency[0].

Fig. 8.8. Poll analysis application.

image

Lines 17–18 take the responses one at a time from array responses and increments one of the 10 counters frequency[1] to frequency[10]; we ignore frequency[0] because the survey responses are limited to the range 1–10. The key statement in the loop appears in line 18, which increments the appropriate frequency counter, depending on the value of responses[answer].

Let’s consider several iterations of the for statement. When control variable answer is 0, the value of responses[answer] is the value of responses[0] (i.e., 1 in line 10), so the application interprets ++frequency[responses[answer]] as

++frequency[ 1 ]

which increments the value in frequency array element 1. To evaluate the expression, start with the value in the innermost set of square brackets, answer. Once you know answer’s value (which is the value of the loop-control variable in line 17), plug it into the expression and evaluate the next outer set of square brackets—i.e., responses[answer], which is a value selected from the responses array in lines 10–12. Then use the resulting value as the index for the frequency array to specify which counter to increment (line 18).

When answer is 1, responses[answer] is the value of responses[1], which is 2, so the application interprets ++frequency[responses[answer]] as

++frequency[ 2 ]

which increments the frequency array element 2.

When answer is 2, responses[answer] is the value of responses[2], which is 6, so the application interprets ++frequency[responses[answer]] as

++frequency[ 6 ]

which increments frequency array element 6, and so on. Regardless of the number of responses processed in the survey, the application requires only an 11-element array (in which we ignore element 0) to summarize the results, because all the response values are between 1 and 10, inclusive, and the index values for an 11-element array are 0 through 10.

If the data in the responses array had contained invalid values, such as 13, the application would have attempted to add 1 to frequency[13], which is outside the bounds of the array. In many programming languages, like C and C++, writing outside the bounds of an array is actually allowed and would overwrite arbitrary information in memory, often causing disastrous results. C# does not allow this—accessing any array element forces a check on the array index to ensure that it’s valid (i.e., it must be greater than or equal to 0 and less than the length of the array). This is called bounds checking. If an application uses an invalid index, the Common Language Runtime generates an exception (specifically, an IndexOutOfRangeException) to indicate that an error occurred in the application at execution time. The condition in a control statement could determine whether an index is valid before allowing it to be used in an array-access expression, thus avoiding the exception.

Error-Prevention Tip 8.1

image

An exception indicates that an error has occurred in an application. You often can write code to recover from an exception and continue application execution, rather than abnormally terminating the application. Exception handling is discussed in Chapter 13.

Error-Prevention Tip 8.2

image

When writing code to loop through an array, ensure that the array index remains greater than or equal to 0 and less than the length of the array. The loop-continuation condition should prevent the accessing of elements outside this range.

8.5 Case Study: Card Shuffling and Dealing Simulation

So far, this chapter’s examples have used arrays of value-type elements. This section uses random-number generation and an array of reference-type elements—namely, objects representing playing cards—to develop a class that simulates card shuffling and dealing. This class can then be used to implement applications that play card games.

We first develop class Card (Fig. 8.9), which represents a playing card that has a face (e.g., "Ace", "Deuce", "Three", ..., "Jack", "Queen", "King") and a suit (e.g., "Hearts", "Diamonds", "Clubs", "Spades"). Next, we develop class DeckOfCards (Fig. 8.10), which creates a deck of 52 playing cards in which each element is a Card object. Then we build an application (Fig. 8.11) that uses class DeckOfCards’s card-shuffling-and-dealing capabilities.

Fig. 8.9. Card class represents a playing card.

image

Fig. 8.10. DeckOfCards class represents a deck of playing cards.

image

image

Fig. 8.11. Card shuffling and dealing application.

image

Class Card

Class Card (Fig. 8.9) contains two string instance variables—face and suit—that are used to store references to the face value and suit name for a specific Card. The constructor for the class (lines 9–13) receives two strings that it uses to initialize face and suit. Method ToString (lines 16–19) creates a string consisting of the face of the card, the string "of" and the suit of the card. Recall from Chapter 7 that the + operator can be used to concatenate (i.e., combine) several strings to form one larger string. Card’s ToString method can be invoked explicitly to obtain a string representation of a Card object (e.g., "Ace of Spades"). The ToString method of an object is called implicitly in many cases when the object is used where a string is expected (e.g., when WriteLine outputs the object or when the object is concatenated to a string using the + operator). For this behavior to occur, ToString must be declared with the header exactly as shown in line 16 of Fig. 8.9. We’ll explain the purpose of the override keyword in more detail when we discuss inheritance in Chapter 11.

Class DeckOfCards

Class DeckOfCards (Fig. 8.10) declares an instance-variable named deck that will refer to an array of Card objects (line 7). Like simple-type array variable declarations, the declaration of a variable for an array of objects includes the type of the elements in the array, followed by square brackets and the name of the array variable. Class DeckOfCards also declares int instance variable currentCard (line 8), representing the next Card to be dealt from the deck array, and named constant NUMBER_OF_CARDS (line 9), indicating the number of Cards in the deck (52).

The class’s constructor instantiates the deck array (line 19) to be of size NUMBER_OF_CARDS. When first created, the elements of the deck array are null by default, so the constructor uses a for statement (lines 24–26) to fill the deck array with Cards. The for statement initializes control variable count to 0 and loops while count is less than deck.Length, causing count to take on each integer value from 0 to 51 (the indices of the deck array). Each Card is instantiated and initialized with two strings—one from the faces array (which contains the strings "Ace" through "King") and one from the suits array (which contains the strings "Hearts", "Diamonds", "Clubs" and "Spades"). The calculation count % 13 always results in a value from 0 to 12 (the 13 indices of the faces array in lines 15–16), and the calculation count / 13 always results in a value from 0 to 3 (the four indices of the suits array in line 17). When the deck array is initialized, it contains the Cards with faces "Ace" through "King" in order for each suit.

Method Shuffle (lines 30–46) shuffles the Cards in the deck. The method loops through all 52 Cards (array indices 0 to 51). For each Card, a number between 0 and 51 is picked randomly to select another Card. Next, the current Card object and the randomly selected Card object are swapped in the array. This exchange is performed by the three assignments in lines 42–44. The extra variable temp temporarily stores one of the two Card objects being swapped. The swap cannot be performed with only the two statements

deck[ first ] = deck[ second ];
deck[ second ] = deck[ first ];

If deck[first] is the "Ace" of "Spades" and deck[second] is the "Queen" of "Hearts", then after the first assignment, both array elements contain the "Queen" of "Hearts", and the "Ace" of "Spades" is lost—hence, the extra variable temp is needed. After the for loop terminates, the Card objects are randomly ordered. Only 52 swaps are made in a single pass of the entire array, and the array of Card objects is shuffled. [Note: It’s recommended that you use a so-called unbiased shuffling algorithm for real card games. Such an algorithm ensures that all possible shuffled card sequences are equally likely to occur. A popular unbiased shuffling algorithm is the Fisher-Yates algorithm—en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle. This page also shows how to implement the algorithm in several programming languages.]

Method DealCard (lines 49–56) deals one Card in the array. Recall that currentCard indicates the index of the next Card to be dealt (i.e., the Card at the top of the deck). Thus, line 52 compares currentCard to the length of the deck array. If the deck is not empty (i.e., currentCard is less than 52), line 53 returns the top Card and increments current-Card to prepare for the next call to DealCard—otherwise, null is returned.

Shuffling and Dealing Cards

The application of Fig. 8.11 demonstrates the card shuffling and dealing capabilities of class DeckOfCards (Fig. 8.10). Line 10 creates a DeckOfCards object named myDeckOfCards. Recall that the DeckOfCards constructor creates the deck with the 52 Card objects in order by suit and face. Line 11 invokes myDeckOfCards’s Shuffle method to rearrange the Card objects. The for statement in lines 14–20 deals all 52 Cards in the deck and displays them in four columns of 13 Cards each. Line 16 deals and displays a Card object by invoking myDeckOfCards’s DealCard method. When Console.Write outputs a Card with string formatting, the Card’s ToString method (declared in lines 16–19 of Fig. 8.9) is invoked implicitly. Because the field width is negative, the result is output left justified in a field of width 19.

8.6 foreach Statement

In previous examples, we demonstrated how to use counter-controlled for statements to iterate through the elements in an array. In this section, we introduce the foreach statement, which iterates through the elements of an entire array or collection. This section discusses how to use the foreach statement to loop through an array. We show how to use it with collections in Chapter 23. The syntax of a foreach statement is:

foreach ( type identifier in arrayName )
   statement

where type and identifier are the type and name (e.g., int number) of the iteration variable, and arrayName is the array through which to iterate. The type of the iteration variable must be consistent with the type of the elements in the array. As the next example illustrates, the iteration variable represents successive values in the array on successive iterations of the foreach statement.

Figure 8.12 uses the foreach statement (lines 13–14) to calculate the sum of the integers in an array of student grades. The type specified is int, because array contains int values—therefore, the loop will select one int value from the array during each iteration. The foreach statement iterates through successive values in the array one by one. The foreach header can be read concisely as “for each iteration, assign the next element of array to int variable number, then execute the following statement.” Thus, for each iteration, identifier number represents the next int value in the array. Lines 13–14 are equivalent to the following counter-controlled repetition used in lines 13–14 of Fig. 8.5 to total the integers in array:

for ( int counter = 0; counter < array.Length; counter++ )
   total += array[ counter ];

Fig. 8.12. Using the foreach statement to total integers in an array.

image

Common Programming Error 8.4

image

The foreach statement can be used only to access array elements—it cannot be used to modify elements. Any attempt to change the value of the iteration variable in the body of a foreach statement will cause a compilation error.

The foreach statement can be used in place of the for statement whenever code looping through an array does not require access to the counter indicating the index of the current array element. For example, totaling the integers in an array requires access only to the element values—the index of each element is irrelevant. However, if an application must use a counter for some reason other than simply to loop through an array (e.g., to display an index number next to each array element value, as in the examples earlier in this chapter), use the for statement.

Implicitly Typed Local Variables

In each for statement presented so far and in the foreach statement of Fig. 8.12, we declared the type of the control variable either in the for or foreach statement’s header. C# provides a new feature—called implicitly typed local variables—that enables the compiler to infer a local variable’s type based on the type of the variable’s initializer. To distinguish such an initialization from a simple assignment statement, the var keyword is used in place of the variable’s type. Recall that a local variable is any variable declared in the body of a method. In the declaration

var x = 7;

the compiler infers that the variable x should be of type int, because the compiler assumes that whole-number values, like 7, are of type int. Similarly, in the declaration

var y = -123.45;

the compiler infers that the variable y should be of type double, because the compiler assumes that floating-point number values, like -123.45, are of type double.

You can also use local type inference with control variables in the header of a for or foreach statement. For example, the for statement header

for ( int counter = 1; counter < 10; counter++ )

can be written as

for ( var counter = 1; counter  < 10; counter++ )

In this case, counter is of type int because it’s initialized with a whole-number value (1). Similarly, assuming that myArray is an array of ints, the foreach statement header

foreach ( int number in myArray )

can be written as

foreach ( var number in myArray )

In this case, number is of type int because it’s used to process elements of the int array myArray. The implicitly typed local-variable feature is one of several new Visual C# 2010 features that support Language Integrated Query (LINQ).

Implicitly typed local variables can be also used to initialize arrays without explicitly giving their type. For example, the following statement creates an array of int values:

var array = new[] { 32, 27, 64, 18, 95, 14, 90, 70, 60, 37 };

There are no square brackets on the left side of the assignment operator, and that new[] is used to specify that the variable is an array. We’ll use implicitly typed local variables when we present LINQ examples in Chapter 9 and several later chapters.

8.7 Passing Arrays and Array Elements to Methods

To pass an array argument to a method, specify the name of the array without any brackets. For example, if hourlyTemperatures is declared as

double[] hourlyTemperatures = new double[ 24 ];

then the method call

ModifyArray( hourlyTemperatures );

passes the reference of array hourlyTemperatures to method ModifyArray. Every array object “knows” its own length (and makes it available via its Length property). Thus, when we pass an array object’s reference to a method, we need not pass the array length as an additional argument.

For a method to receive an array reference through a method call, the method’s parameter list must specify an array parameter. For example, the method header for method ModifyArray might be written as

void ModifyArray( double[] b )

indicating that ModifyArray receives the reference of an array of doubles in parameter b. The method call passes array hourlyTemperature’s reference, so when the called method uses the array variable b, it refers to the same array object as hourlyTemperatures in the calling method.

When an argument to a method is an entire array or an individual array element of a reference type, the called method receives a copy of the reference. However, when an argument to a method is an individual array element of a value type, the called method receives a copy of the element’s value. To pass an individual array element to a method, use the indexed name of the array as an argument in the method call. If you want to pass a value-type array element to a method by reference, you must use the ref keyword as shown in Section 7.16.

Figure 8.13 demonstrates the difference between passing an entire array and passing a value-type array element to a method. The foreach statement at lines 17–18 outputs the five elements of array (an array of int values). Line 20 invokes method ModifyArray, passing array as an argument. Method ModifyArray (lines 37–41) receives a copy of array’s reference and uses the reference to multiply each of array’s elements by 2. To prove that array’s elements (in Main) were modified, the foreach statement at lines 24–25 outputs the five elements of array again. As the output shows, method ModifyArray doubled the value of each element.

Fig. 8.13. Passing arrays and individual array elements to methods.

image

image

Figure 8.13 next demonstrates that when a copy of an individual value-type array element is passed to a method, modifying the copy in the called method does not affect the original value of that element in the calling method’s array. To show the value of array[3] before invoking method ModifyElement, lines 27–29 output the value of array[3], which is 8. Line 31 calls method ModifyElement and passes array[3] as an argument. Remember that array[3] is actually one int value (8) in array. Therefore, the application passes a copy of the value of array[3]. Method ModifyElement (lines 44–49) multiplies the value received as an argument by 2, stores the result in its parameter element, then outputs the value of element (16). Since method parameters, like local variables, cease to exist when the method in which they’re declared completes execution, the method parameter element is destroyed when method ModifyElement terminates. Thus, when the application returns control to Main, lines 32–33 output the unmodified value of array[3] (i.e., 8).

8.8 Passing Arrays by Value and by Reference

In C#, a variable that “stores” an object, such as an array, does not actually store the object itself. Instead, such a variable stores a reference to the object. The distinction between reference-type variables and value-type variables raises some subtle issues that you must understand to create secure, stable programs.

As you know, when an application passes an argument to a method, the called method receives a copy of that argument’s value. Changes to the local copy in the called method do not affect the original variable in the caller. If the argument is of a reference type, the method makes a copy of the reference, not a copy of the actual object that’s referenced. The local copy of the reference also refers to the original object, which means that changes to the object in the called method affect the original object.

Performance Tip 8.1

image

Passing arrays and other objects by reference makes sense for performance reasons. If arrays were passed by value, a copy of each element would be passed. For large, frequently passed arrays, this would waste time and would consume considerable storage for the copies of the arrays—both of these problems cause poor performance.

In Section 7.16, you learned that C# allows variables to be passed by reference with keyword ref. You can also use keyword ref to pass a reference-type variable by reference, which allows the called method to modify the original variable in the caller and make that variable refer to a different object. This is a subtle capability, which, if misused, can lead to problems. For instance, when a reference-type object like an array is passed with ref, the called method actually gains control over the reference itself, allowing the called method to replace the original reference in the caller with a reference to a different object, or even with null. Such behavior can lead to unpredictable effects, which can be disastrous in mission-critical applications. The application in Fig. 8.14 demonstrates the subtle difference between passing a reference by value and passing a reference by reference with keyword ref.

Fig. 8.14. Passing an array reference by value and by reference.

image

image

image

image

Lines 11 and 14 declare two integer array variables, firstArray and firstArrayCopy. Line 11 initializes firstArray with the values 1, 2 and 3. The assignment statement at line 14 copies the reference stored in firstArray to variable firstArrayCopy, causing these variables to reference the same array object. We make the copy of the reference so that we can determine later whether reference firstArray gets overwritten. The for statement at lines 23–24 displays the contents of firstArray before it’s passed to method FirstDouble (line 27) so that we can verify that the called method indeed changes the array’s contents.

The for statement in method FirstDouble (lines 83–84) multiplies the values of all the elements in the array by 2. Line 87 creates a new array containing the values 11, 12 and 13, and assigns the array’s reference to parameter array in an attempt to overwrite reference firstArray in the caller—this, of course, does not happen, because the reference was passed by value. After method FirstDouble executes, the for statement at lines 33–34 displays the contents of firstArray, demonstrating that the values of the elements have been changed by the method. The if...else statement at lines 37–42 uses the == operator to compare references firstArray (which we just attempted to overwrite) and firstArray-Copy. The expression in line 37 evaluates to true if the operands of operator == reference the same object. In this case, the object represented by firstArray is the array created in line 11—not the array created in method FirstDouble (line 87)—so the original reference stored in firstArray was not modified.

Lines 45–76 perform similar tests, using array variables secondArray and second-ArrayCopy, and method SecondDouble (lines 92–100). Method SecondDouble performs the same operations as FirstDouble, but receives its array argument using keyword ref. In this case, the reference stored in secondArray after the method call is a reference to the array created in line 99 of SecondDouble, demonstrating that a variable passed with keyword ref can be modified by the called method so that the variable in the caller actually points to a different object—in this case, an array created in SecondDouble. The if...else statement in lines 71–76 confirms that secondArray and secondArrayCopy no longer refer to the same array.

Software Engineering Observation 8.1

image

When a method receives a reference-type parameter by value, a copy of the object’s reference is passed. This prevents a method from overwriting references passed to that method. In the vast majority of cases, protecting the caller’s reference from modification is the desired behavior. If you encounter a situation where you truly want the called procedure to modify the caller’s reference, pass the reference-type parameter using keyword ref—but, again, such situations are rare.

Software Engineering Observation 8.2

image

In C#, objects (including arrays) are effectively passed by reference, because references to objects are passed to called methods. A called method receiving a reference to an object in a caller can interact with, and possibly change, the caller’s object.

8.9 Case Study: Class GradeBook Using an Array to Store Grades

This section further evolves class GradeBook, introduced in Chapter 4 and expanded in Chapters 56. Recall that this class represents a grade book used by an instructor to store and analyze a set of student grades. Previous versions of the class process a set of grades entered by the user, but do not maintain the individual grade values in instance variables of the class. Thus, repeat calculations require the user to re-enter the same grades. One way to solve this problem would be to store each grade entered in an individual instance of the class. For example, we could create instance variables grade1, grade2, ..., grade10 in class GradeBook to store 10 student grades. However, the code to total the grades and determine the class average would be cumbersome, and the class would not be able to process any more than 10 grades at a time. In this section, we solve this problem by storing grades in an array.

Storing Student Grades in an Array in Class GradeBook

The version of class GradeBook (Fig. 8.15) presented here uses an array of integers to store the grades of several students on a single exam. This eliminates the need to repeatedly input the same set of grades. Variable grades (which will refer to an array of ints) is declared as an instance variable in line 7—therefore, each GradeBook object maintains its own set of grades. The class’s constructor (lines 14–18) has two parameters—the name of the course and an array of grades. When an application (e.g., class GradeBookTest in Fig. 8.16) creates a GradeBook object, the application passes an existing int array to the constructor, which assigns the array’s reference to instance variable grades (line 17). The size of array grades is determined by the class that passes the array to the constructor.

Fig. 8.15. Grade book using an array to store test grades.

image

image

image

image

Fig. 8.16. Create a GradeBook object using an array of grades.

image

image

Thus, a GradeBook object can process a variable number of grades—as many as are in the array in the caller. The grade values in the passed array could have been input from a user at the keyboard or read from a file on disk (as discussed in Chapter 17). In our test application, we simply initialize an array with a set of grade values (Fig. 8.16, line 9). Once the grades are stored in instance variable grades of class GradeBook, all the class’s methods can access the elements of grades as needed to perform various calculations.

Method ProcessGrades (lines 29–43) contains a series of method calls that result in the output of a report summarizing the grades. Line 32 calls method OutputGrades to display the contents of array grades. Lines 126–128 in method OutputGrades use a for statement to output the student grades. A for statement, rather than a foreach, must be used in this case, because lines 127–128 use counter variable student’s value to output each grade next to a particular student number (see Fig. 8.16). Although array indices start at 0, an instructor would typically number students starting at 1. Thus, lines 127–128 output student + 1 as the student number to produce grade labels "Student 1: ", "Student 2: " and so on.

Method ProcessGrades next calls method GetAverage (line 35) to obtain the average of the grades in the array. Method GetAverage (lines 78–88) uses a foreach statement to total the values in array grades before calculating the average. The iteration variable in the foreach’s header (e.g., int grade) indicates that for each iteration, int variable grade takes on a value in array grades. The averaging calculation in line 87 uses grades.Length to determine the number of grades being averaged.

Lines 38–39 in method ProcessGrades call methods GetMinimum and GetMaximum to determine the lowest and highest grades of any student on the exam, respectively. Each of these methods uses a foreach statement to loop through array grades. Lines 51–56 in method GetMinimum loop through the array, and lines 54–55 compare each grade to lowGrade. If a grade is less than lowGrade, lowGrade is set to that grade. When line 58 executes, lowGrade contains the lowest grade in the array. Method GetMaximum (lines 62–75) works the same way as method GetMinimum.

Finally, line 42 in method ProcessGrades calls method OutputBarChart to display a distribution chart of the grade data, using a technique similar to that in Fig. 8.6. In that example, we manually calculated the number of grades in each category (i.e., 0–9, 10–19, ..., 90–99 and 100) by simply looking at a set of grades. In this example, lines 99–100 use a technique similar to that in Figs. 8.7 and 8.8 to calculate the frequency of grades in each category. Line 96 declares variable frequency and initializes it with an array of 11 ints to store the frequency of grades in each grade category. For each grade in array grades, lines 99–100 increment the appropriate element of the frequency array. To determine which element to increment, line 100 divides the current grade by 10, using integer division. For example, if grade is 85, line 100 increments frequency[8] to update the count of grades in the range 80–89. Lines 103–117 next display the bar chart (see Fig. 8.6) based on the values in array frequency. Like lines 24–25 of Fig. 8.6, lines 113–114 of Fig. 8.15 use a value in array frequency to determine the number of asterisks to display in each bar.

Class GradeBookTest That Demonstrates Class GradeBook

The application of Fig. 8.16 creates an object of class GradeBook (Fig. 8.15) using int array gradesArray (declared and initialized in line 9). Lines 11–12 pass a course name and gradesArray to the GradeBook constructor. Line 13 displays a welcome message, and line 14 invokes the GradeBook object’s ProcessGrades method. The output reveals the summary of the 10 grades in myGradeBook.

Software Engineering Observation 8.3

image

A test harness (or test application) is responsible for creating an object of the class being tested and providing it with data. This data could come from any of several sources. Test data can be placed directly into an array with an array initializer, it can come from the user at the keyboard or it can come from a file (as you’ll see in Chapter 17). After passing this data to the class’s constructor to instantiate the object, the test harness should call the object to test its methods and manipulate its data. Gathering data in the test harness like this allows the class to manipulate data from several sources.

8.10 Multidimensional Arrays

Multidimensional arrays with two dimensions are often used to represent tables of values consisting of information arranged in rows and columns. To identify a particular table element, we must specify two indices. By convention, the first identifies the element’s row and the second its column. Arrays that require two indices to identify a particular element are called two-dimensional arrays. (Multidimensional arrays can have more than two dimensions, but such arrays are beyond the scope of this book.) C# supports two types of two-dimensional arrays—rectangular arrays and jagged arrays.

Rectangular Arrays

Rectangular arrays are used to represent tables of information in the form of rows and columns, where each row has the same number of columns. Figure 8.17 illustrates a rectangular array named a containing three rows and four columns—a three-by-four array. In general, an array with m rows and n columns is called an m-by-n array.

Fig. 8.17. Rectangular array with three rows and four columns.

image

Every element in array a is identified in Fig. 8.17 by an array-access expression of the form a[row, column]; a is the name of the array, and row and column are the indices that uniquely identify each element in array a by row and column number. The names of the elements in row 0 all have a first index of 0, and the names of the elements in column 3 all have a second index of 3.

Like one-dimensional arrays, multidimensional arrays can be initialized with array initializers in declarations. A rectangular array b with two rows and two columns could be declared and initialized with nested array initializers as follows:

int[ , ] b = { { 1, 2 }, { 3, 4 } };

The initializer values are grouped by row in braces. So 1 and 2 initialize b[0, 0] and b[0, 1], respectively, and 3 and 4 initialize b[1, 0] and b[1, 1], respectively. The compiler counts the number of nested array initializers (represented by sets of two inner braces within the outer braces) in the initializer list to determine the number of rows in array b. The compiler counts the initializer values in the nested array initializer for a row to determine the number of columns (two) in that row. The compiler will generate an error if the number of initializers in each row is not the same, because every row of a rectangular array must have the same length.

Jagged Arrays

A jagged array is maintained as a one-dimensional array in which each element refers to a one-dimensional array. The manner in which jagged arrays are represented makes them quite flexible, because the lengths of the rows in the array need not be the same. For example, jagged arrays could be used to store a single student’s exam grades across multiple classes, where the number of exams may vary from class to class.

We can access the elements in a jagged array by an array-access expression of the form arrayName[row][column]—similar to the array-access expression for rectangular arrays, but with a separate set of square brackets for each dimension. A jagged array with three rows of different lengths could be declared and initialized as follows:

int[][] jagged = { new int[] { 1, 2 },
                   new int[] { 3 },
                   new int[] { 4, 5, 6 } };

In this statement, 1 and 2 initialize jagged[0][0] and jagged[0][1], respectively; 3 initializes jagged[1][0]; and 4, 5 and 6 initialize jagged[2][0], jagged[2][1] and jagged[2][2], respectively. Therefore, array jagged in the preceding declaration is actually composed of four separate one-dimensional arrays—one that represents the rows, one containing the values in the first row ({1, 2}), one containing the value in the second row ({3}) and one containing the values in the third row ({4,5,6}). Thus, array jagged itself is an array of three elements, each a reference to a one-dimensional array of int values.

Observe the differences between the array-creation expressions for rectangular arrays and for jagged arrays. Two sets of square brackets follow the type of jagged, indicating that this is an array of int arrays. Furthermore, in the array initializer, C# requires the keyword new to create an array object for each row. Figure 8.18 illustrates the array reference jagged after it’s been declared and initialized.

Fig. 8.18. Jagged array with three rows of different lengths.

image

Creating Two-Dimensional Arrays with Array-Creation Expressions

A rectangular array can be created with an array-creation expression. For example, the following lines declare variable b and assign it a reference to a three-by-four rectangular array:

int[ , ] b;
b = new int[ 3, 4 ];

In this case, we use the literal values 3 and 4 to specify the number of rows and number of columns, respectively, but this is not required—applications can also use variables and expressions to specify array dimensions. As with one-dimensional arrays, the elements of a rectangular array are initialized when the array object is created.

A jagged array cannot be completely created with a single array-creation expression. The following statement is a syntax error:

int[][] c = new int[ 2 ][ 5 ]; // error

Instead, each one-dimensional array in the jagged array must be initialized separately. A jagged array can be created as follows:

int[][] c;
c = new int[ 2 ][ ]; // create 2 rows
c[ 0 ] = new int[ 5 ]; // create 5 columns for row 0
c[ 1 ] = new int[ 3 ]; // create 3 columns for row 1

The preceding statements create a jagged array with two rows. Row 0 has five columns, and row 1 has three columns.

Two-Dimensional Array Example: Displaying Element Values

Figure 8.19 demonstrates initializing rectangular and jagged arrays with array initializers and using nested for loops to traverse the arrays (i.e., visit every element of each array).

Fig. 8.19. Initializing jagged and rectangular arrays.

image

image

Class InitArray’s Main method creates two arrays. Line 12 uses nested array initializers to initialize variable rectangular with an array in which row 0 has the values 1, 2 and 3, and row 1 has the values 4, 5 and 6. Lines 17–19 uses nested initializers of different lengths to initialize variable jagged. In this case, the initializer uses the keyword new to create a one-dimensional array for each row. Row 0 is initialized to have two elements with values 1 and 2, respectively. Row 1 is initialized to have one element with value 3. Row 2 is initialized to have three elements with the values 4, 5 and 6, respectively.

Method OutputArray has been overloaded with two versions. The first version (lines 27–40) specifies the array parameter as int[,] array to indicate that it takes a rectangular array. The second version (lines 43–56) takes a jagged array, because its array parameter is listed as int[][] array.

Line 21 invokes method OutputArray with argument rectangular, so the version of OutputArray at lines 27–40 is called. The nested for statement (lines 32–39) outputs the rows of a rectangular array. The loop-continuation condition of each for statement (lines 32 and 35) uses the rectangular array’s GetLength method to obtain the length of each dimension. Dimensions are numbered starting from 0, so the method call GetLength(0) on array returns the size of the first dimension of the array (the number of rows), and the call GetLength(1) returns the size of the second dimension (the number of columns).

Line 23 invokes method OutputArray with argument jagged, so the version of OutputArray at lines 43–56 is called. The nested foreach statement (lines 48–55) outputs the rows of a jagged array. The inner foreach statement (lines 51–52) iterates through each element in the current row of the array. This allows the loop to determine the exact number of columns in each row. Since the jagged array is created as an array of arrays, we can use nested foreach statements to output the elements in the console window. The outer loop iterates through the elements of array, which are references to one-dimensional arrays of int values that represent each row. The inner loop iterates through the elements of the current row. A foreach statement can also iterate through all the elements in a rectangular array. In this case, foreach iterates through all the rows and columns starting from row 0, as if the elements were in a one-dimensional array.

Common Multidimensional-Array Manipulations Performed with for Statements

Many common array manipulations use for statements. As an example, the following for statement sets all the elements in row 2 of rectangular array a in Fig. 8.17 to 0:

for ( int column = 0; column < a.GetLength( 1 ); column++)
   a[ 2, column ] = 0;

We specified row 2; therefore, we know that the first index is always 2 (0 is the first row, and 1 is the second row). This for loop varies only the second index (i.e., the column index). The preceding for statement is equivalent to the assignment statements

a[ 2, 0 ] = 0;
a[ 2, 1 ] = 0;
a[ 2, 2 ] = 0;
a[ 2, 3 ] = 0;

The following nested for statement totals the values of all the elements in array a:

image

These nested for statements total the array elements one row at a time. The outer for statement begins by setting the row index to 0 so that row 0’s elements can be totaled by the inner for statement. The outer for then increments row to 1 so that row 1’s elements can be totaled. Then the outer for increments row to 2 so that row 2’s elements can be totaled. The variable total can be displayed when the outer for statement terminates. In the next example, we show how to process a rectangular array in a more concise manner using foreach statements.

8.11 Case Study: GradeBook Using a Rectangular Array

In Section 8.9, we presented class GradeBook (Fig. 8.15), which used a one-dimensional array to store student grades on a single exam. In most courses, students take several exams. Instructors are likely to want to analyze grades across the entire course, both for a single student and for the class as a whole.

Storing Student Grades in a Rectangular Array in Class GradeBook

Figure 8.20 contains a version of class GradeBook that uses a rectangular array grades to store the grades of a number of students on multiple exams. Each row of the array represents a single student’s grades for the entire course, and each column represents the grades for the whole class on one of the exams the students took during the course. An application such as GradeBookTest (Fig. 8.21) passes the array as an argument to the GradeBook constructor. In this example, we use a 10-by-3 array containing 10 students’ grades on three exams. Five methods perform array manipulations to process the grades. Each method is similar to its counterpart in the earlier one-dimensional-array version of class GradeBook (Fig. 8.15). Method GetMinimum (lines 44–58) determines the lowest grade of any student for the semester. Method GetMaximum (lines 61–75) determines the highest grade of any student for the semester. Method GetAverage (lines 78–90) determines a particular student’s semester average. Method OutputBarChart (lines 93–122) outputs a bar chart of the distribution of all student grades for the semester. Method OutputGrades (lines 125–149) outputs the two-dimensional array in tabular format, along with each student’s semester average.

Fig. 8.20. Grade book using rectangular array to store grades.

image

image

image

image

image

Fig. 8.21. Create GradeBook object using a rectangular array of grades.

image

image

Methods GetMinimum, GetMaximum and OutputBarChart each loop through array grades using the foreach statement—for example, the foreach statement from method GetMinimum (lines 50–55). To find the lowest overall grade, this foreach statement iterates through rectangular array grades and compares each element to variable lowGrade. If a grade is less than lowGrade, lowGrade is set to that grade.

When the foreach statement traverses the elements of the array grades, it looks at each element of the first row in order by index, then each element of the second row in order by index and so on. The foreach statement in lines 50–55 traverses the elements of grade in the same order as the following equivalent code, expressed with nested for statements:

image

When the foreach statement completes, lowGrade contains the lowest grade in the rectangular array. Method GetMaximum works similarly to method GetMinimum.

Method OutputBarChart (lines 93–122) displays the grade distribution as a bar chart. The syntax of the foreach statement (lines 101–104) is identical for one-dimensional and two-dimensional arrays.

Method OutputGrades (lines 125–149) uses nested for statements to output values of the array grades, in addition to each student’s semester average. The output in Fig. 8.21 shows the result, which resembles the tabular format of an instructor’s physical grade book. Lines 131–132 display the column headings for each test. We use the for statement rather than the foreach statement here so that we can identify each test with a number. Similarly, the for statement in lines 137–148 first outputs a row label using a counter variable to identify each student (line 139). Although array indices start at 0, lines 132 and 139 output test + 1 and student + 1, respectively, to produce test and student numbers starting at 1 (see Fig. 8.21). The inner for statement in lines 142–143 uses the outer for statement’s counter variable student to loop through a specific row of array grades and output each student’s test grade. Finally, line 147 obtains each student’s semester average by passing the row index of grades (i.e., student) to method GetAverage.

Method GetAverage (lines 78–90) takes one argument—the row index for a particular student. When line 147 calls GetAverage, the argument is int value student, which specifies the particular row of rectangular array grades. Method GetAverage calculates the sum of the array elements on this row, divides the total by the number of test results and returns the floating-point result as a double value (line 89).

Class GradeBookTest That Demonstrates Class GradeBook

The application in Fig. 8.21 creates an object of class GradeBook (Fig. 8.20) using the two-dimensional array of ints that gradesArray references (lines 9–18). Lines 20–21 pass a course name and gradesArray to the GradeBook constructor. Lines 22–23 then invoke myGradeBook’s DisplayMessage and ProcessGrades methods to display a welcome message and obtain a report summarizing the students’ grades for the semester, respectively.

8.12 Variable-Length Argument Lists

Variable-length argument lists allow you to create methods that receive an arbitrary number of arguments. A one-dimensional array-type argument preceded by the keyword params in a method’s parameter list indicates that the method receives a variable number of arguments with the type of the array’s elements. This use of a params modifier can occur only in the last entry of the parameter list. While you can use method overloading and array passing to accomplish much of what is accomplished with variable-length argument lists, using the params modifier is more concise.

Figure 8.22 demonstrates method Average (lines 8–17), which receives a variable-length sequence of doubles (line 8). C# treats the variable-length argument list as a one-dimensional array whose elements are all of the same type. Hence, the method body can manipulate the parameter numbers as an array of doubles. Lines 13–14 use the foreach loop to walk through the array and calculate the total of the doubles in the array. Line 16 accesses numbers.Length to obtain the size of the numbers array for use in the averaging calculation. Lines 31, 33 and 35 in Main call method Average with two, three and four arguments, respectively. Method Average has a variable-length argument list, so it can average as many double arguments as the caller passes. The output reveals that each call to method Average returns the correct value.

Fig. 8.22. Using variable-length argument lists.

image

image

Common Programming Error 8.5

image

The params modifier may be used only with the last parameter of the parameter list.

8.13 Using Command-Line Arguments

On many systems, it’s possible to pass arguments from the command line (these are known as command-line arguments) to an application by including a parameter of type string[] (i.e., an array of strings) in the parameter list of Main, exactly as we have done in every application in the book. By convention, this parameter is named args (Fig. 8.23, line 7). When an application is executed from the Command Prompt, the execution environment passes the command-line arguments that appear after the application name to the application’s Main method as strings in the one-dimensional array args. The number of arguments passed from the command line is obtained by accessing the array’s Length property. For example, the command "MyApplication a b" passes two command-line arguments to application MyApplication. Command-line arguments are separated by white space, not commas. When the preceding command executes, the Main method entry point receives the two-element array args (i.e., args.Length is 2) in which args[0] contains the string "a" and args[1] contains the string "b". Common uses of command-line arguments include passing options and file names to applications.

Fig. 8.23. Using command-line arguments to initialize an array.

image

image

Figure 8.23 uses three command-line arguments to initialize an array. When the application executes, if args.Length is not 3, the application displays an error message and terminates (lines 10–13). Otherwise, lines 16–32 initialize and display the array based on the values of the command-line arguments.

The command-line arguments become available to Main as strings in args. Line 17 gets args[0]—a string that specifies the array size—and converts it to an int value, which the application uses to create the array in line 18. The static method ToInt32 of class Convert converts its string argument to an int. Lines 21–22 convert the args[1] and args[2] command-line arguments to int values and store them in initialValue and increment, respectively. Lines 25–26 calculate the value for each array element.

The first sample execution indicates that the application received an insufficient number of command-line arguments. The second sample execution uses command-line arguments 5, 0 and 4 to specify the size of the array (5), the value of the first element (0) and the increment of each value in the array (4), respectively. The corresponding output indicates that these values create an array containing the integers 0, 4, 8, 12 and 16. The output from the third sample execution illustrates that the command-line arguments 10, 1 and 2 produce an array whose 10 elements are the nonnegative odd integers from 1 to 19.

8.14 Wrap-Up

This chapter began our data structures introduction. We used arrays to store data in and retrieve data from lists and tables of values. We demonstrated how to declare array variables, initialize arrays and refer to individual array elements. We introduced foreach as an additional means (besides the for statement) for iterating through arrays. We showed how to pass arrays to methods and how to declare and manipulate multidimensional arrays. Finally, the chapter showed how to write methods that use variable-length argument lists and how to read arguments passed to an application from the command line.

We continue our data structures coverage in Chapter 9 where we discuss List collections—dynamically resizable array-based collections. Chapter 21, Data Structures, introduces dynamic data structures, such as lists, queues, stacks and trees, that can grow and shrink as applications execute. Chapter 22, Generics, presents generics, which provide the means to create general models of methods and classes that can be declared once, but used with many data types. Chapter 23, Collections, introduces the data structure classes provided by the .NET Framework Class Library, some of which use generics to allow you to specify the exact types of objects that a particular data structure will store. You can use these predefined data structures instead of building your own. The .NET Framework Class Library also provides class Array, which contains utility methods for array manipulation. Chapter 23 uses several static methods of class Array to perform such manipulations as sorting and searching the data in an array.

In Chapter 9 we introduce Language Integrated Query (LINQ), which enables you to write expressions that can retrieve information from a wide variety of data sources, such as arrays and collections. You’ll see how to search, sort and filter data using LINQ.

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

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