This section presents several examples that demonstrate declaring arrays, creating arrays, initializing arrays and manipulating array elements.
In Fig. 8.2, line 10 of uses an array-creation expression to create a five-element int
array with values initialized to 0
by default. The resulting array’s reference initializes the variable array
to refer to the new array object.
Line 12 displays column headings for the app’s output. The first column will display each array element’s index (0
–4
for a five-element array), and the second column contains each element’s default value (0
). The column head "Value"
is right-aligned in a field width of 8, as specified in the string
-interpolation expression
{"Value",8}
The for
statement (lines 15–18) displays each array element’s index (represented by counter
) and value (represented by array[counter]
). The loop-control variable counter
is initially 0
—index values start at 0, so zero-based counting allows the loop to access every element. The loop-continuation condition uses the property array.Length
(line 15) to obtain array
’s length. In this example, the length is 5
, so the loop continues executing as long as counter
’s value is less than 5. The highest index in a five-element array is 4, 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 4
). We’ll soon see what happens when an out-of-range index is encountered at execution time.
An app can create an array and initialize its elements with an array initializer—a comma-separated list of expressions (called an initializer list) enclosed in braces. 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—the compiler counts the number of initializers (5) to determine the array’s size, then sets up the appropriate new
operation “behind the scenes.” The app in Fig. 8.3 initializes an integer array with 5 values (line 10) and displays the array in tabular format. Lines 15–18 for displaying the array’s contents are identical to lines 15–18 of Fig. 8.2.
Figure 8.4 creates a 5-element array and assigns to its elements the even integers from 2 to 10 (2
, 4
, 6
, 8
and 10
). Then the app displays the array in tabular format. Lines 13–16 calculate an array element’s value by multiplying the current value of the for
loop’s control variable counter
by 2
, then adding 2
.
const
Line 9 uses the modifier const
to declare the constant ArrayLength
, which is initialized to 5
. Constants must be initialized in their declarations and cannot be modified thereafter. Constants use the same Pascal Case naming conventions as classes, methods and properties.
Constants are also called named constants. Apps using constants often are more readable than those that use literal values (e.g., 5
)—a named constant such as ArrayLength
clearly indicates its purpose, whereas the literal value 5
could have different meanings based on the context in which it’s used. Another advantage to using named constants is that if the constant’s value must be changed, the change is necessary only in the declaration, thus reducing code-maintenance costs.
Defining the size of an array as a named constant instead of a literal makes code clearer. This technique eliminates so-called magic numbers. For example, repeatedly mentioning the size 5 in array-processing code for a five-element array gives the number 5 an artificial significance and can be confusing when the program includes other 5s that have nothing to do with the array size.
Assigning a value to a named constant after it’s been initialized is a compilation error.
Attempting to declare a named constant without initializing it is a compilation error.
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 sum the elements then use the sum to calculate the class average. The GradeBook
examples later in the chapter (Sections 8.8 and 8.10) use this technique. Figure 8.5 sums the values contained in a 10-element int
array, which is declared and initialized in line 9. The for
statement performs the calculations by adding each element’s value to the total
(line 15).3
foreach
So far, we’ve used counter-controlled for
statements to iterate through array elements. In this section, we introduce the foreach
statement, which iterates through an entire array’s elements (or the elements of a collection, as you’ll see in Section 9.4). 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 iteration variable’s type must be consistent with the array’s element type. The iteration variable represents successive values in the array on successive iterations of the foreach
statement.
Figure 8.6 uses the foreach
statement (lines 13–16) to calculate the sum of array
’s elements. The iteration variable number
’s type is declared as int
(line 13), because array
contains int
values. The foreach
statement iterates through successive int
values in array
one by one, starting with the first element. The foreach
header can be read concisely as “for each iteration, assign array
’s next element to int
variable number
, then execute the following statement.” Lines 13–16 are equivalent to the counter-controlled iteration used in lines 13–16 of Fig. 8.5.
Any attempt to change the iteration variable’s value in the body of a foreach
statement results in a compilation error.
foreach
vs. for
The foreach
statement can be used in place of the for
statement whenever code looping through an array does not require access to the current array element’s index. For example, totaling the integers in an array requires access only to the element values—each element’s index is irrelevant. If an app must use a counter for some reason other than simply to loop through an array (e.g., to calculate an element’s value based on the counter’s value, as in Fig. 8.4), you should use the for
statement.
Attempting to modify an array element’s value using a foreach
statement’s iteration variable is a logic error—the iteration variable can be used only to access each array element’s value, not modify it.
var
Many apps 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 an exam. Suppose the grades on an exam were 87, 68, 94, 100, 83, 78, 85, 91, 76 and 87. Included were 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 app (Fig. 8.7) 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
in Sections 8.8 and 8.10 contain code that calculates these grade frequencies based on a set of grades. For now, we manually create array
and initialize it with the number of grades in each range (Fig. 8.7, line 9). We discuss the keyword var
(lines 14 and 27) after we present the app’s logic.
The app 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–24 display a grade range (e.g., "70-79: "
) based on the current value of counter
. When counter
is 10
, line 19 displays " 100: "
to align the colon with the other bar labels. When counter
is not 10
, line 23 uses the string
-interpolation expressions
{counter * 10:D2}
and
{counter * 10 + 9:D2}
to format 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 27–30) displays the bars. Note the loop-continuation condition at line 27 (stars < array[counter]
). Each time the app 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 0
s because no students received a grade below 60. Thus, the app displays no asterisks next to the first six grade ranges.
var
In line 14
for (var counter = 0; counter < array.Length; ++counter)
notice var
keyword rather than a type preceding the variable counter
. This declares the variable and lets the compiler determine the variable’s type, based on the variable’s initializer. This process is known as type inference and local variables declared in this manner are known as implicitly typed local variables. Here, the compiler infers that counter
’s type is int
, because it’s initialized with the literal 0
, which is an int
.
Similarly, consider line 11 of Fig. 4.9:
Account account1 = new Account("Jane Green");
Notice that the type Account
appears twice—once to declare variable account1
’s type and once to specify the type of the new object being created. From this point forward, the preferred way to write this statement is
var account1 = new Account("Jane Green");
Here, the compiler infers that account1
’s type is Account
, because the compiler can determine the type from the expression
new Account("Jane Green")
which creates an Account
object.
The Microsoft C# Coding Conventions
https://msdn.microsoft.com/library/ff926074
recommend using type inference when a local variable’s type is obvious, based on its initial value.4 These coding conventions are just guidelines, not requirements. In industry, your employer might have its own coding requirements that differ from Microsoft’s guidelines.
Implicitly typed local variables also can be used to initialize an array variable via an initializer list. In the following statement, the type of values
is inferred as int[]
:
var values = new[] {32, 27, 64, 18, 95, 14, 90, 70, 60, 37};
new[]
specifies that the initializer list is for an array. The array’s element type, int
, is inferred from the initializers. The following statement—in which values
is initialized directly without new[]
—generates a compilation error:
var values = {32, 27, 64, 18, 95, 14, 90, 70, 60, 37};
Initializer lists can be used with both arrays and collections. If an implicitly typed local variable is initialized via an initializer list without new[]
, a compilation error occurs, because the compiler cannot infer whether the variable should be an array or a collection. We use a List
collection in Chapter 9 and cover collections in detail in Chapter 21.
Sometimes, apps use counter variables to summarize data, such as the results of a survey. In Fig. 7.7, we used separate counters in our die-rolling app to track the number of times each face of a six-sided die appeared as the app rolled the die 60,000,000 times. An array version of the app in Fig. 7.7 is shown in Fig. 8.8.
The app uses array frequency
(line 10) to count the occurrences of each roll. The single statement in line 15 replaces lines 24–44 of Fig. 7.7. Line 15 of Fig. 8.8 uses the random value to determine which frequency
array element to increment. The call to Next
produces a random number from 1 to 6, so 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 directly as an index for array frequency
. We also replaced lines 48–50 of Fig. 7.7 by looping through array frequency
to output the results (Fig. 8.8, lines 21–24).