Arrays






Arrays

One particular aspect of variable declaration that Chapter 1 didn't cover is array declaration. With array declaration, you can store multiple items of the same type using a single variable and still access them individually using the index when required. In C#, the array index starts at zero. Therefore, arrays in C# are zero based.

Beginner Topic: Arrays

Arrays provide a means of declaring a collection of data items that are of the same type using a single variable. Each item within the array is uniquely designated using an integer value called the index. The first item in a C# array is accessed using index 0. Programmers should be careful to specify an index value that is less than the array size. Since C# arrays are zero based, the index for the last element in an array is one less than the total number of items in the array.


Declaring an Array

In C#, you declare arrays using square brackets. First, you specify the type of the items within the array, followed by open and closed square brackets; then you enter the name of the variable. Listing 2.28 declares a variable called languages to be an array of strings.

Declaring an Array

string[] languages;

Obviously, the first part of the array identifies the data type of the elements within the array. The square brackets that are part of the declaration identify the rank, or the number of dimensions, for the array; in this case it is an array of rank one. These two pieces form the data type for the variable languages.

Language Contrast: C++ and JavaArray Declaration

The square brackets for an array in C# appear immediately following the data type instead of after the variable declaration. This keeps all the type information together instead of splitting it up both before and after the identifier, as occurs in C++ and Java.


Listing 2.28 defines an array with a rank of one. Commas within the square brackets define additional dimensions. Listing 2.29, for example, defines a two-dimensional array of cells for a game of chess or tic-tac-toe.

Declaring a Two-Dimensional Array

//    |   |
// ---+---+---
//    |   |
// ---+---+---
//    |   |
int[,] cells;

In Listing 2.29, the array has a rank of two. The first dimension could correspond to cells going across and the second dimension represents cells going down. Additional dimensions are added, with additional commas, and the total rank is one more than the number of commas. Note that the number of items that occur for a particular dimension is not part of the variable declaration. This is specified when creating (instantiating) the array and allocating space for each element.

Instantiating and Assigning Arrays

Once an array is declared, you can immediately fill its values using a comma-delimited list of items enclosed within a pair of curly braces. Listing 2.30 declares an array of strings and then assigns the names of nine languages within curly braces.

Array Declaration with Assignment

string[] languages = { "C#", "COBOL", "Java",
  "C++", "Visual Basic", "Pascal",
  "Fortran", "Lisp", "J#"};

The first item in the comma-delimited list becomes the first item in the array; the second item in the list becomes the second item in the array, and so on. The curly brackets are the notation for defining an array literal.

The assignment syntax shown in Listing 2.30 is available only if you declare and assign the value within one statement. To assign the value after declaration requires the use of the keyword new and the corresponding data type, as shown in Listing 2.31.

Array Assignment Following Declaration

string[] languages;
languages = new string[]{"C#", "COBOL", "Java",
    "C++", "Visual Basic", "Pascal",
    "Fortran", "Lisp", "J#" };

C# also allows use of the new keyword as part of the declaration statement, so it allows the assignment and the declaration shown in Listing 2.32.

Listing 2.32. Array Assignment with new during Declaration

string[] languages = new string[]{
    "C#", "COBOL", "Java",
    "C++", "Visual Basic", "Pascal",
    "Fortran", "Lisp", "J#"};

The use of the new keyword tells the runtime to allocate memory for the data type. It instructs the runtime to instantiate the data typein this case, an array.

Whenever you use the new keyword as part of an array assignment, you may also specify the size of the array within the square brackets. Listing 2.33 demonstrates this syntax.

Listing 2.33. Declaration and Assignment with the new Keyword

string[] languages = new string[9]{
    "C#", "COBOL", "Java",
    "C++", "Visual Basic", "Pascal",
    "Fortran", "Lisp", "J#"};

The array size in the initialization statement and the number of elements contained within the curly braces must match. Furthermore, it is possible to assign an array but not specify the initial values of the array, as demonstrated in Listing 2.34.

Assigning without Literal Values

string[] languages = new string[9];

Assigning an array but not initializing the initial values will still initialize each element. The runtime initializes elements to their default values, as follows.

  • Reference types (such as string) are initialized to null.

  • Numeric types are initialized to zero.

  • bool is initialized to false.

  • char is initialized to \0.

As a result, it is not necessary to individually assign each element of an array before using it.

In C# 2.0, it is possible to use the default() operator to determine the default value of a data type. default() takes a data type as a parameter.default(int), for example, returns 0 and default(char) returns \0.

Because the array size is not included as part of the variable declaration, it is possible to specify the size at runtime. For example, Listing 2.35 creates an array based on the size specified in the Console.ReadLine() call.

Defining the Array Size at Runtime

string[] groceryList;
System.Console.Write("How many items on the list? ");
int size = int.Parse(System.Console.ReadLine());
groceryList = new string[size];                                            

// ...

C# initializes multidimensional arrays similarly. A comma separates the size of each rank. Listing 2.36 initializes a tic-tac-toe board with no moves.

Declaring a Two-Dimensional Array

int[,] cells = int[3,3];

Initializing a tic-tac-toe board with a specific position instead could be done as shown in Listing 2.37.

Initializing a Two-Dimensional Array of Integers

int[,] cells = {
        {1, 0, 2},
        {1, 2, 0},
        {1, 2, 1}
    };

The initialization follows the pattern in which there is an array of three elements of type int[], and each element has the same size; in this example, the size is three. Note that the dimension of each int[] element must be identical. The declaration shown in Listing 2.38, therefore, is not valid.

A Multidimensional Array with Inconsistent Size, Causing an Error

// ERROR: Each dimension must be consistently sized.
int[,] cells = {
        {1, 0, 2, 0},
        {1, 2, 0},
        {1, 2}
        {1}
   };

Representing tic-tac-toe does not require an integer in each position. One alternative is a separate virtual board for each player, with each board containing a bool that indicates which positions the players selected. Listing 2.39 corresponds to a three-dimensional board.

Initializing a Three-Dimensional Array

bool[,,] cells;
cells = new bool[2,3,3]
  {
     // Player 1 moves                // X |  |
     {   {true, false, false},        // ---+---+---
         {true, false, false},        // X |  |
         {true, false, true} },       // ---+---+---
                                              // X |  | X
  // Player 2 moves                     //  |  | O
   { {false,         false, true},    // ---+---+---
     {false,         true, false},    //  | O |
     {false,         true, true} }    // ---+---+---
                                             //  | O |
 };

In this example, the board is initialized and the size of each rank is explicitly identified. In addition to identifying the size as part of the new expression, the literal values for the array are provided. The literal values of type bool[,,] are broken into two arrays of type bool[,], size 3x3. Each two-dimensional array is composed of three bool arrays, size 3.

As already mentioned, each dimension in a multidimensional array must be consistently sized. However, it is also possible to define a jagged array, which is an array of arrays. Jagged array syntax is slightly different from that of a multidimensional array, and furthermore, jagged arrays do not need to be consistently sized. Therefore, it is possible to initialize a jagged array as shown in Listing 2.40.

Initializing a Jagged Array

int [][] cells= {
   new int[] {1, 0, 2, 0},
   new int[] {1, 2, 0},
   new int[] {1, 2},
   new int[] {1}
};

A jagged array doesn't use a comma to identify a new dimension. Rather, a jagged array defines an array of arrays. In Listing 2.40, [] is placed after the data type int[], thereby declaring an array of type int[].

Notice that a jagged array requires an array instance for each internal array. In this example, you use new to instantiate the internal element of the jagged arrays. Leaving out the instantiation would cause a compile error.

Using an Array

You access a specific item in an array using the square bracket notation, known as the array accessor. To retrieve the first item from an array, you specify zero as the index. In Listing 2.41, the value of the fifth item (using the index 4 because the first item is index 0) in the languages variable is stored in the variable language.

Declaring and Accessing an Array

string[] languages = new string[9]{
    "C#", "COBOL", "Java",
    "C++", "Visual Basic", "Pascal",
    "Fortran", "Lisp", "J#"};
// Retrieve 3rd item in languages array (Java)
string language = languages[2];

The square bracket notation is also used to store data into an array. Listing 2.42 switches the order of "C++" and "Java".

Swapping Data between Positions in an Array

string[] languages = new string[9]{
    "C#", "COBOL", "Java",
    "C++", "Visual Basic", "Pascal",
    "Fortran", "Lisp", "J#"};
// Save "C++" to variable called language.
string language = languages[3];
// Assign "Java" to the C++ position.
languages[3] = languages[2];
// Assign language to location of "Java".
languages[2] = language;

For multidimensional arrays, an element is identified with an index for each dimension, as shown in Listing 2.43.

Initializing a Two-Dimensional Array of Integers

int[,] cells = {
        {1, 0, 2},
        {0, 2, 0},
        {1, 2, 1}
    };
// Set the winning tic-tac-toe move to be player 1.
cells[1,0] = 1;

Jagged array element assignment is slightly different because it is consistent with the jagged array declaration. The first element is an array within the array of arrays. The second index specifies the item within the selected array element (see Listing 2.44).

Declaring a Jagged Array

int[][] cells = {
    new int[]{1, 0, 2},
    new int[]{0, 2, 0},
    new int[]{1, 2, 1}
};

cells [1][0] = 1;
// ...

Length

You can obtain the length of an array, as shown in Listing 2.45.

Retrieving the Length of an Array

Console.WriteLine("There are {0} languages in the array.",
    languages.Length

Arrays have a fixed length; they are bound such that the length cannot be changed without re-creating the array. Furthermore, overstepping the bounds (or length) of the array will cause the runtime to report an error. This can occur by accessing (either retrieving or assigning) the array with an index for which no element exists in the array. Such an error frequently occurs when you use the array length as an index into the array, as shown in Listing 2.46.

Accessing Outside the Bounds of an Array, Throwing an Exception

string languages = new string[9];
...
// RUNTIME ERROR: index out of bounds  should
// be 8 for the last element
languages[ 4] = languages[ 9];

Note

The Length member returns the number of items in the array, not the highest index. The Length member for the languages variable is 9, but the highest index for the languages variable is 8.


It is a good practice to use Length in place of the hardcoded array size. To use Length as an index, for example, it is necessary to subtract 1 to avoid an out-of-bounds error (see Listing 2.47).

Language Contrast: C++Buffer Overflow Bugs

Unmanaged C++ does not always check whether you overstep the bounds on an array. Not only can this be difficult to debug, but making this mistake can also result in a potential security error called a buffer overrun. In contrast, the Common Language Runtime protects all C# (and Managed C++) code from overstepping array bounds, eliminating the possibility of a buffer overrun issue in managed code.


Listing 2.47. Using Length- 1 in the Array Index

string languages = new string[9];
...
languages[4] = languages[languages.Length - 1];

To avoid overstepping the bounds on an array use Length - 1, as demonstrated in Listing 2.47, in place of a hardcoded value accessing the last item in the array.

Length returns the total number of elements in an array. Therefore, if you had a multidimensional array such as boolcells[,,] of size 2·3·3, Length would return the total number of elements, 18.

More Array Methods

Arrays include additional methods for manipulating the elements within the array. These include Sort(), BinarySearch(), Reverse(), and Clear() (see Listing 2.48).

Additional Array Methods

class ProgrammingLanguages
{
  static void Main()
  {
     string[] languages = new string[]{
         "C#", "COBOL", "Java",
         "C++", "Visual Basic", "Pascal",
         "Fortran", "Lisp", "J#"};

     System.Array.Sort(languages);                                         
     searchString = "COBOL";
   index = System.Array.BinarySearch(                                      
         languages, searchString);                                         
     System.Console.WriteLine(
         "The wave of the future, {0}, is at index {1}.",
         searchString, index);

     System.Console.WriteLine();
     System.Console.WriteLine("{0,-20}\t{1,-20}",
         "First Element", "Last Element");
     System.Console.WriteLine("{0,-20}\t{1,-20}",
         "-------------", "------------");
     System.Console.WriteLine("{0,-20}\t{1,-20}",
         languages[0], languages[languages.Length-1]);

     System.Array.Reverse(languages);                                      
     System.Console.WriteLine("{0,-20}\t{1,-20}",
         languages[0], languages[languages.Length-1]);

     // Note this does not remove all items from the array.
     // Rather it sets each item to the type's default value.

     System.Array.Clear(languages, 0, languages.Length);                    
     System.Console.WriteLine("{0,-20}\t{1,-20}",
         languages[0], languages[languages.Length-1]);
     System.Console.WriteLine(
         "After clearing, the array size is: {0}",
         languages.Length);
  }
}

The results of Listing 2.48 are shown in Output 2.19.

Output 2.19.

The wave of the future, COBOL, is at index 1.
First Element        Last Element
-------------    ------------
C#                    Visual Basic
Visual Basic          C#
After clearing, the array size is: 9

Access to these methods is on the System.Array class. For the most part, using these methods is self-explanatory, except for two noteworthy items.

  • Before using the BinarySearch() method, it is important to sort the array. If values are not sorted in increasing order, then the incorrect index may be returned. If the search element does not exist, then the value returned is negative. (Using the complement operator, ~index, returns the first index, if any, that is larger than the searched value.)

  • The Clear() method does not remove elements of the array and does not set the length to zero. The array size is fixed and cannot be modified. Therefore, the Clear() method sets each element in the array to its default value (false, 0, or null). This explains why Console.WriteLine() creates a blank line when writing out the array after Clear() is called.

Language Contrast: Visual BasicRedimensioning Arrays

Visual Basic includes a Redim statement for changing the number of items in an array. Although there is no equivalent C# specific keyword, there is a method available in .NET 2.0 that will re-create the array and then copy all the elements over to the new array. The method is called System.Array.Resize.


Array Instance Methods

Like strings, arrays have instance members that are accessed not from the data type, but rather, directly from the variable. Length is an example of an instance member because access to Length is through the array variable, not the class. Other significant instance members are GetLength(), Rank, and Clone().

Retrieving the length of a particular dimension does not require the Length property. To retrieve the size of a particular rank, an array includes a GetLength() instance method. When calling this method, it is necessary to specify the rank whose length will be returned (see Listing 2.49).

Retrieving a Particular Dimension's Size

bool[,,] cells;
cells = new bool[2,3,3];
System.Console.WriteLine(cells.GetLength(0));  // Displays 2

The results of Listing 2.49 appear in Output 2.20.

Output 2.20.

2

Listing 2.49 displays 2 because this is the number of elements in the first dimension.

It is also possible to retrieve the entire array's rank by accessing the array's Rank member. cells.Rank, for example, will return 3.

By default, assigning one array variable to another copies only the array reference, not the individual elements of the array. To make an entirely new copy of the array, use the array's Clone() method. The Clone() method will return a copy of the array; changing any of the members of this new array will not affect the members of the original array.

Strings as Arrays

Variables of type string are accessible like an array of characters. For example, to retrieve the fourth character of a string called palindrome you can call palindrome[3]. Note, however, that because strings are immutable, it is not possible to assign particular characters within a string. C#, therefore, would not allow palindrome[3]='a', where palindrome is declared as a string. Listing 2.50 uses the array accessor to determine whether an argument on the command line is an option, where an option is identified by a dash as the first character.

Looking for Command-Line Options

string[] args;
...
if(args[0][0]=='-')
{
     // This parameter is an option
}

This snippet uses the if statement, which is covered in Chapter 3. In addition, it presents an interesting example because you use the array accessor to retrieve the first element in the array of strings, args. Following the first array accessor is a second one, this time to retrieve the first character of the string. The code, therefore, is equivalent to that shown in Listing 2.51.

Looking for Command-Line Options (Simplified)

string[] args;
...
string arg = args[0];
if(arg[0] == '-')
{
     // This parameter is an option
}

Not only can string characters be accessed individually using the array accessor, it is also possible to retrieve the entire string as an array of characters using the string's ToCharArray() method. Using this method, you could reverse the string using the System.Array.Reverse() method, as demonstrated in Listing 2.52, which determines if a string is a palindrome.

Reversing a String

class Palindrome
{
  static void Main()
  {
      string reverse, palindrome;
      char[] temp;

      System.Console.Write("Enter a palindrome: ");
      palindrome = System.Console.ReadLine();

      // Remove spaces and convert to lowercase
      reverse = palindrome.Replace(" ", "");
      reverse = reverse.ToLower();

      // Convert to an array                                                
      temp = reverse.ToCharArray();                                         
     // Reverse the array                                                   
            System.Array.Reverse(temp);                                       

      
      // Convert the array back to a string and
      
      // check if reverse string is the same.
      if(reverse == new string(temp))
      {
          System.Console.WriteLine("\"{0}\" is a palindrome.",
               palindrome);
      }
     else
     {
        System.Console.WriteLine(
            "\"{0}\" is NOT a palindrome.",
            palindrome);
     }
  }
}

The results of Listing 2.52 appear in Output 2.21.

Output 2.21.

Enter a palindrome: NeverOddOrEven
"NeverOddOrEven" is a palindrome.

This example uses the new keyword; this time, it creates a new string from the reversed array of characters.

Common Errors

This section introduced the three different types of arrays: single-dimension, multidimensional, and jagged arrays. Several rules and idiosyncrasies govern array declaration and use. Figure points out some of the most common errors and helps solidify the rules. Readers should consider reviewing the code in the Common Mistake column first (without looking at the Error Description and Corrected Code columns) as a way of verifying their understanding of arrays and their syntax.

Common Array Coding Errors

Common Mistake

Error Description

Corrected Code

int numbers[];

The square braces for declaring an array appear after the data type, not after the variable identifier.

int[] numbers;

int[] numbers;
numbers = {42, 84, 168 };

When assigning an array after declaration, it is necessary to use the new keyword and then specify the data type.

int[] numbers;
numbers = -new -int[]{
      42, 84, 168 }

int[3] numbers =
    { 42, 84, 168 };

It is not possible to specify the array size as part of the variable declaration.

int[] numbers =
     { 42, 84, 168 };

int[] numbers =
     new int[];

The array size is required at initialization time unless an array literal is provided.

int[] numbers =
     new int[3];

int[] numbers =
     new int[3]{}

The array size is specified as 3, but there are no elements in the array literal. The array size must match the number of elements in the array literal.

int[] numbers =
    new int[3] { 42, 84, 168 };

int[] numbers =
     new int[3];
Console.WriteLine(
     numbers[3]);

Array indexes start at zero. Therefore, the last item is one less than the array size. (Note that this is a runtime error, not a compile-time error.)

int[] numbers =
     new int[3];
Console.WriteLine(
     numbers[2]);

int[] numbers =
    new int[3];
numbers[numbers.Length] =
42;

Same as previous error: 1 needs to be subtracted from the Length to access the last element. (Note that this is a runtime error, not a compile-time error.)

int[] numbers =
    new int[3];
numbers[numbers.Length-1] =
    42;

int[] numbers;
Console.WriteLine(
    numbers[0]);

numbers has not yet been assigned an instantiated array, and therefore, it cannot be accessed.

int[] numbers = {42, 84};
Console.WriteLine(
numbers[0]);

int[,] numbers =
    { {42},
      {84, 42} };

Multidimensional arrays must be structured consistently.

int[,] numbers =
    { {42, 168},
      {84, 42} };

int[][] numbers =
    { {42, 84},
    {84, 42} };

Jagged arrays require instantiated arrays to be specified for the arrays within the array.

int[][] numbers =
    { new int[]{42, 84},
      new int[]{84, 42} };




 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows