Chapter 10. Arrays

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;

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.

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.

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.

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.

The foreach statement allows you to iterate through all the items in an array or other collection, examining each item in turn. The syntax for the foreach statement is:

    foreach (type identifier in expression)statement

You can update Example 10-1 to replace the final two for statements (that iterate over the contents of the populated array) with foreach statements, as shown in Example 10-2.

The output will be identical.

You can initialize the contents of an array at the time it is instantiated by providing a list of values delimited by curly brackets ({}). C# provides a longer and a shorter syntax:

    int[] myIntArray = new int[5] { 2, 4, 6, 8, 10 };
    int[] myIntArray = { 2, 4, 6, 8, 10 };

In the shorter syntax, C# automatically creates an array of the proper size for the number of elements in the brackets. There is no practical difference between these two statements, and most programmers will use the shorter syntax.

The params keyword allows you to pass in a variable number of parameters of the same type to a method. What the method receives is an array of that type.

In the next example, you create a method, DisplayVals( ) , that takes a variable number of integer arguments:

    public void DisplayVals(params int[] intVals)

You are free to iterate over the array as you would over any other array of integers:

    foreach (int i in intVals)
    {
       Console.WriteLine("DisplayVals {0}",i);
    }

The calling method, however, need not explicitly create an array: it can simply pass in integers, and the compiler will assemble the parameters into an array for the DisplayVals( ) method:

    t.DisplayVals(5,6,7,8);

You are free to pass in an array if you prefer:

    int [] explicitArray = new int[5] {1,2,3,4,5};
    t.DisplayVals(explicitArray);

Example 10-3 illustrates using the params keyword.

The output looks like this:

    DisplayVals 5
    DisplayVals 6
    DisplayVals 7
    DisplayVals 8
    DisplayVals 1
    DisplayVals 2
    DisplayVals 3
    DisplayVals 4
    DisplayVals 5

Arrays can be thought of as long rows of slots into which values can be placed. Once you have a picture of a row of slots, imagine 10 rows, one on top of another. This is the classic two-dimensional array of rows and columns. The rows run across the array and the columns run up and down the array, as shown in Figure 10-2.

A third dimension is a bit harder to imagine. Okay, now imagine four dimensions. Now imagine 10.

Those of you who are not string-theory physicists have probably given up, as have I. Multidimensional arrays are useful, however, even if you can’t quite picture what they would look like.

C# supports two types of multidimensional arrays: rectangular and jagged. In a rectangular array, every row is the same length. A jagged array, however, is an array of arrays, each of which can be a different length.

A rectangular array is an array of two (or more) dimensions. In the classic two-dimensional array, the first dimension is the number of rows and the second dimension is the number of columns.

To declare a two-dimensional array, use the following syntax:

type [,]array-name

For example, to declare and instantiate a two-dimensional rectangular array named myRectangularArray that contains two rows and three columns of integers, you would write:

    int [,] myRectangularArray = new int[2,3];

Example 10-4 declares, instantiates, initializes, and prints the contents of a two-dimensional array. In this example, a for loop is used to initialize the elements of the array.

The output looks like this:

    rectangularArray[0,0] = 0
    rectangularArray[0,1] = 1
    rectangularArray[0,2] = 2
    rectangularArray[1,0] = 1
    rectangularArray[1,1] = 2
    rectangularArray[1,2] = 3
    rectangularArray[2,0] = 2
    rectangularArray[2,1] = 3
    rectangularArray[2,2] = 4
    rectangularArray[3,0] = 3
    rectangularArray[3,1] = 4
    rectangularArray[3,2] = 5

The brackets in the int[,] declaration indicate that the type is an array of integers, and the comma indicates the array has two dimensions (two commas would indicate three dimensions, and so on). The actual instantiation of rectangularArray with new int[rows, columns] sets the size of each dimension. Here, the declaration and instantiation have been combined.

The program fills the rectangle with a pair of nested for loops, iterating through each column in each row. Thus, the first element filled is rectangularArray[0,0], followed by rectangularArray[0,1] and rectangularArray[0,2]. Once this is done, the program moves on to the next rows: rectangularArray[1,0], rectangularArray[1,1], rectangularArray[1,2], and so forth, until all the columns in all the rows are filled.

Just as you can initialize a one-dimensional array using bracketed lists of values, you can initialize a two-dimensional array using similar syntax. Example 10-5 declares a two-dimensional array (rectangularArray), initializes its elements using bracketed lists of values, and then prints out the contents.

The output looks like this:

    rectangularArrayrectangularArray[0,0] = 0
    rectangularArrayrectangularArray[0,1] = 1
    rectangularArrayrectangularArray[0,2] = 2
    rectangularArrayrectangularArray[1,0] = 3
    rectangularArrayrectangularArray[1,1] = 4
    rectangularArrayrectangularArray[1,2] = 5
    rectangularArrayrectangularArray[2,0] = 6
    rectangularArrayrectangularArray[2,1] = 7
    rectangularArrayrectangularArray[2,2] = 8
    rectangularArrayrectangularArray[3,0] = 9
    rectangularArrayrectangularArray[3,1] = 10
    rectangularArrayrectangularArray[3,2] = 11

The preceding example is very similar to Example 10-4, but this time you imply the exact dimensions of the array by how you initialize it:

    int[,] rectangularArrayrectangularArray =
    {
        {0,1,2}, {3,4,5}, {6,7,8}, {9,10,11}
    };

Assigning values in four bracketed lists, each consisting of three elements, implies a 4 (rows) by 3 (columns) array.

Had you written this as:

    int[,] rectangularArrayrectangularArray =
    {
       {0,1,2,3}, {4,5,6,7}, {8,9,10,11}
    };

you would instead have implied a 3 by 4 array.

You can see that the C# compiler understands the implications of the way you grouped the input values, because it is able to access the objects with the appropriate offsets, as illustrated in the output.

C# arrays are “smart” and they keep track of their bounds. When you imply a 4 × 3 array, you must treat it as such, and not as a 3 × 4 array, or just an array of 12 integers (as you can with some other C-family languages).

A jagged array is an array of arrays. It is called “jagged” because each of the rows need not be the same size as all the others, and thus a graphical representation of the array would not be square.

When you create a jagged array, you declare the number of rows in your array. Each row will hold an array, which can be of any length. These arrays must each be declared. You can then fill in the values for the elements in these “inner” arrays.

In a jagged array, each dimension is a one-dimensional array. To declare a jagged array, use the following syntax, where the number of brackets indicates the number of dimensions of the array:

type [] []...

For example, you would declare a two-dimensional jagged array of integers named myJaggedArray, as follows:

    int [] [] myJaggedArray;

Access the fifth element of the third array by writing myJaggedArray[2][4].

Example 10-6 creates a jagged array named myJaggedArray, initializes its elements, and then prints their content. To save space, the program takes advantage of the fact that integer array elements are automatically initialized to zero, and it initializes the values of only some of the elements.

The output looks like this:

    jaggedArray[0][0] = 0
    jaggedArray[0][1] = 0
    jaggedArray[0][2] = 0
    jaggedArray[0][3] = 15
    jaggedArray[0][4] = 0
    jaggedArray[1][0] = 0
    jaggedArray[1][1] = 12
    jaggedArray[2][0] = 0
    jaggedArray[2][1] = 9
    jaggedArray[2][2] = 99
    jaggedArray[3][0] = 10
    jaggedArray[3][1] = 11
    jaggedArray[3][2] = 12
    jaggedArray[3][3] = 13
    jaggedArray[3][4] = 14

In this example, a jagged array is created with four rows:

    int[][] jaggedArray = new int[rows][];

Notice that the second dimension is not specified. This is set by creating a new array for each row. Each of these arrays can have a different size:

    // the first row has 5 elements
    jaggedArray[0] = new int[5];

    // a row with 2 elements
    jaggedArray[1] = new int[2];

    // a row with 3 elements
    jaggedArray[2] = new int[3];

    // the last row has 5 elements
    jaggedArray[3] = new int[5];

Once an array is specified for each row, you need only populate the various members of each array and then print out their contents to ensure that all went as expected.

Another way of outputting the values would be to use two nested for loops, and use the Length property of the array to control the loop:

    for (int i = 0; i < jaggedArray.Length; i++ )
    {
       for (int j = 0; j < jaggedArray[i].Length; j++)
       {
          Console.WriteLine("jaggedArray[{0}][{1}] = {2}",
          i, j, jaggedArray[i][j]);
       }
    }

In this case, the “outer” for loop iterates over the rows in the array. The “inner” loop outputs each column in the given row. Because you’re using Length to control how many times the loop runs, it doesn’t matter that each row is a different length.

Notice that when you access the members of the rectangular array, you put the indexes all within one set of square brackets:

    rectangularArrayrectangularArray[i,j]

while with a jagged array, you need a pair of brackets:

    jaggedArray[i][j]

You can keep this straight by thinking of the first as a single array of more than one dimension and the jagged array as an array of arrays .

Although you’ve been using arrays as built-in types throughout this chapter, an array is actually an object of type System.Array .[7] Arrays in C# thus provide you with the best of both worlds: easy-to-use syntax underpinned with an actual class definition so that instances of an array have access to the methods and properties of System.Array. You’ve seen the Length property of arrays used several times already. Some of the other important methods and properties appear in Table 10-1.

Two useful static methods from Table 10-1 that deserve a closer look are Sort( ) and Reverse( ). These methods do what you think they would: Reverse( ) reverses the order of elements in the array, and Sort( ) sorts the elements in order. These are fully supported for arrays of the built-in C# types, such as string, so sorting an array of strings puts the elements in alphabetical order, and sorting an array of ints puts them in numeric order. Making the Sort( ) method work with your own classes is a bit trickier, as you must implement the IComparable interface (see Chapter 13 for more on interfaces). Example 10-7 demonstrates the use of these two methods to manipulate String objects.

The output looks like this:

    Value: Proust
    Value: Faulkner
    Value: Mann
    Value: Hugo

    Value: Hugo
    Value: Mann
    Value: Faulkner
    Value: Proust

    Value: We
    Value: Hold
    Value: These
    Value: Truths
    Value: To
    Value: Be
    Value: Self
    Value: Evident

    Value: Be
    Value: Evident
    Value: Hold
    Value: Self
    Value: These
    Value: To
    Value: Truths
    Value: We

The example begins by creating myArray, an array of strings with the words:

    "Proust", "Faulkner", "Mann", "Hugo"

This array is printed, and then passed to the Array.Reverse( ) method, where it is printed again to see that the array itself has been reversed:

    Value: Hugo
    Value: Mann
    Value: Faulkner
    Value: Proust

Similarly, the example creates a second array, myOtherArray, containing the words:

    "We", "Hold", "These", "Truths",
    "To", "Be", "Self", "Evident",

This is passed to the Array.Sort( ) method. Then Array.Sort( ) happily sorts them alphabetically:

    Value: Be
    Value: Evident
    Value: Hold
    Value: Self
    Value: These
    Value: To
    Value: Truths
    Value: We

Tip

Array.Sort( ) and Array.Reverse( ) are static methods, meaning you call them on the class, not the object, as discussed in Chapter 7. That means you don’t call myArray.Reverse( ) to reverse the elements; instead, you call the static method and pass in the array as an argument, like this:

    Array.Reverse(myArray);


[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.

[7] Of course, when you create an array with int[] myArray = new int[5], what you actually create in the IL code is an instance of System.int32[]. Because this derives from the Abstract Base Class System.Array, however, it is fair to say you’ve created an instance of a System.Array.