Most of the examples in previous chapters have dealt with one object at a time. In many applications, however, you want to work with a collection of objects all at the same time. The simplest collection in C# is the array, the only collection type for which C# provides built-in support. The other collection types, such as stack and queue, are not part of the language; they are part of the Framework Class Library. The collection classes are covered in detail in Chapter 14. In this chapter, you will learn to work with three types of arrays : one-dimensional arrays, multidimensional rectangular arrays, and jagged arrays.
Before we get to generic syntax, it makes sense to begin with the simplest collection: the array. An array is an indexed collection of objects, all of the same type (all ints, all strings, and so on). C# provides native syntax for the declaration of arrays. To picture a one-dimensional array, imagine a series of mailboxes, all lined up one after the other, as shown in Figure 10-1. Each mailbox can hold exactly one object (letter, box, and so on). Each mailbox also has a number, so you can identify which item is in which box. Unlike real-world mailboxes, though, all the mailboxes must hold the same kind of object; you declare the type of object that the mailboxes will hold when you declare the array.
The important thing about arrays is that you can treat the entire array (the set of mailboxes) as a single entity, with a single name. As you’ll see, using loops, you can easily perform an operation on every element within an array in turn.
Declare a C# array with the following syntax:
type
[]array-name
;
For example:
int[] myIntArray;
You are not actually declaring an array. Technically, you are declaring a variable
(myIntArray
) that will hold a
reference to an array of integers. As always, we’ll use the
shorthand and refer to myIntArray
as the array, knowing that what we really mean is that it is a
variable that holds a reference to an (unnamed) array.
The square brackets ([]
) tell
the C# compiler that you are declaring an array, and the type
specifies the type of the elements it will contain. In the previous example, myIntArray
is an array of integers.
Instantiate an array using the new
keyword. For example:
myIntArray = new int[5];
This statement creates and intializes an array of five integers, all of which are initialized to the value zero.
It is important to distinguish between the array itself (which
is a collection) and the elements held in the array (which can be of
any type, so long as all the elements in the array are the same type).
myIntArray
is the array (or, more
accurately, the variable that holds the reference to the array); its
elements are the five integers it holds.
C# arrays are reference types, created on the heap. Thus, the
array to which the variable myIntArray
refers is allocated on the heap.
The elements of an array are allocated based on
their own type. Because integers are value types, the elements in
myIntArray
will be value types, and
thus all the elements will be created inside the block of memory
allocated for the array.
The block of memory allocated to an array of reference types will contain references to the actual elements, which are themselves created on the heap in memory separate from that allocated for the array.
When you create an array of value types, each element initially contains the default value for the type stored in the array (see Table 3-1). The statement:
myIntArray = new int[5];
creates an array of five integers, each of whose value is set to 0, which is the default value for integer types.
Unlike arrays of value types, the reference types in an array are not initialized to their default value. Instead, the references held in the array are initialized to null. If you attempt to access an element in an array of reference types before you have specifically initialized the elements, you will generate an exception.
Assume you have created a Button
class. Declare an array of Button
objects with the following
statement:
Button[] myButtonArray;
and instantiate the actual array like this:
myButtonArray = new Button[3];
You can shorten this to:
Button[] myButtonArray = new Button[3];
This statement does not create an array
with references to three Button
objects. Instead, this creates the array myButtonArray
with three null references. To
use this array, you must first construct and assign the Button
objects for each reference in the
array. You can construct the objects in a loop that adds them one by
one to the array, as you’ll see later in the chapter.
You can access the elements of an array using the index
operator ([]
). Arrays are
zero-based, which means that the index of the first element is always
zero—in this case, myArray[0]
.
The Length
property of the array tells you how many objects the
array holds. Therefore, objects are indexed from 0
to Length-1
.
Example 10-1
illustrates the array concepts covered so far. In this example, a
class named Tester
creates an array
of Employees
and an array of
integers, populates the Employee
array, and then prints the values of both.
Example 10-1. Working with an array
using System; namespace Learning_CSharp { // a simple class to store in the array public class Employee { public Employee(int empID) { this.empID = empID; } public override string ToString( ) { return empID.ToString( ); } private int empID; } public class Tester { static void Main( ) { int[] intArray; Employee[] empArray; intArray = new int[5]; empArray = new Employee[3]; // populate the arrays for (int i = 0;i<intArray.Length;i++) { intArray[i] = i*2; } for (int i = 0;i<empArray.Length;i++) { empArray[i] = new Employee(i+1005); } // output array values Console.WriteLine("intArray values:"); for (int i = 0;i<intArray.Length;i++) { Console.WriteLine(intArray[i].ToString( )); } Console.WriteLine(" employee IDs:"); for (int i = 0;i<empArray.Length;i++) { Console.WriteLine(empArray[i].ToString( )); } } } }
The output looks like this:
intArray values: 0 2 4 6 8 employee IDs: 1005 1006 1007
The example starts with the definition of an Employee
class that implements a constructor
that takes a single integer parameter. Employee
implements the ToString( )
method to print the value of the
Employee
object’s employee
ID.
The test method declares and then instantiates a pair of arrays.
Initially, the integer array is automatically filled with integers
whose value is set to 0, but then you populate it with a for
loop. The loop has a counter, i
, and you iterate
through the loop by setting each element of the array as the index
value (i
) increases. You set the
value in intArray
with this
statement:
intArray[i] = i*2;
The first time through the loop, i
is equal to 0, and intArray[0]
is set to 0*2
, which is 0. The next time through,
i
is 1, and the second element
(intArray[1]
) is set to the value 2
(1*2
). The loop runs until i
is equal to intArray.Length
, at which point it stops.
Note that i
is equal to intArray.Length - 1
on the last time through
the loop, which is the index of the last element in the array.
It’s not hard to remember that arrays start at 0, but it can
be tricky to remember that the highest index is actually
one less than Length
. When you use a loop to iterate
through your array, always make sure that your condition for ending
the loop is counter < Array.Length
, not counter <= Array.Length
.
C# is smarter than its C++ and C ancestors; if you do try to write past the end of the array, rather than trampling on random memory, you’ll get an “IndexOutOfRange” exception, which will help you find the error quickly and reliably.
In any case, you want always to be on the lookout for what programmers call the “off-by-one” error. A famous example of this is the fence-post error. Imagine you are building a picket fence, where each horizontal board is one foot long. How many uprights do you need to build a 10-foot fence?[6]
The Employee
array contents
must be constructed by hand, because the values of reference types are
not initialized when the array is created (the array is filled with
null values). You assign values to the elements of empArray
the same
way you did intArray
, with a
for
loop. This time, though, you
have to create a new Employee
object to add to each index in the array
Finally, the contents of the arrays are displayed to ensure that
they are filled as intended, using the same for
loop technique. The five integers print
their value first, followed by the three Employee
objects.
[6] It is tempting to say 10 uprights for a 10-foot fence. But how many do you need for a one-foot fence? You need two, one on each end of the one-foot board. For a two-foot fence, you need three. You always need that extra upright, and so for a 10-foot fence, you need 11 uprights. Watch out for off-by-one errors; they come up a lot.