Team LiB   Previous Section   Next Section

15.1 Arrays

An array is an indexed collection of objects, all of the same type (e.g., all ints, all strings, etc.). C# provides a native syntax for the declaration of Array objects:

int[] myArray;

When you declare an array, you actually create an instance of the Array class in the System namespace (System.Array). Arrays in C# thus provide you with the best of both worlds: easy-to-use syntax built into the language, underpinned with an actual class definition so that instances of an array have access to the methods and properties of System.Array. The System.Array class is discussed in detail later in this chapter.

15.1.1 Declaring Arrays

You declare a C# array with the following syntax:

type[] array-name;

The square brackets ([]) tell the C# compiler that you are declaring an array. (Note that the square brackets are also called the index operator.) The type specifies the type of the elements the array will contain. For example, the following declaration specifies an array called myIntArray that will contain objects of type int (integer):

int[] myIntArray;

Once you declare an array, you must also instantiate it using the new keyword. For example, the following declaration sets aside memory for an array holding five integers:

myIntArray = new int[5];

It is important to distinguish between the array itself (which is a collection of elements) and the elements within the array. myIntArray is the array; its elements are the five integers it holds.

While C# arrays are reference types, created on the heap, the elements of an array are allocated based on their type. Thus, myIntArray is a reference type allocated on the heap and the integer elements in myIntArray are value types allocated on the stack. (While you can box a value type so that it can be treated like a reference type, as explained in Chapter 11, it is not necessary or desirable to box the integers in an array.) By contrast, an array that contains reference types, such as Employee or Button, will contain nothing but references to the elements, which are themselves created on the heap.

15.1.2 Understanding Default Values

When you create an array of value types, each element initially contains the default value for the type stored in the array. (See Table 5-1 in Chapter 5.) The following declaration creates an array of five integers, each of whose value is initialized to 0, the default value for integer types:

myIntArray = new int[5];

With an array of reference types, the elements are not initialized to their default values. Instead, they are initialized to null. If you attempt to access any of the elements in an array of reference types before you specifically initialize them, you will generate an exception (exceptions are covered in Chapter 18).

Assume you have created a Button class. Declare an array of Button objects (thus reference types) with the following statement:

Button[] myButtonArray;

and instantiate the actual array, to hold three Buttons, like this:

myButtonArray = new Button[3];

Note that you can combine the two steps and write:

Button myButtonArray = new Button[3];

In either case, unlike with the earlier integer example, this statement does not create an array with references to three Button objects. Since Button objects are reference types, this creates the array myButtonArray with three null references. To use this array, you must first construct and assign a Button object for each reference in the array. This is called populating the array. You can construct the objects in a loop that adds them one by one to the array. Example 15-1 illustrates creating an array of value types (integers) and reference types (Employee objects).

Example 15-1. Creating an array
using System;

namespace ArrayDemo
{

    // a simple class to store in the array
    public class Employee
    {
        private int empID;

        // constructor
        public Employee(int empID)
        {
            this.empID = empID;
        }
    }

    class Tester
    {
        public void Run()
        {
            int[] intArray;
            Employee[] empArray;
            intArray = new int[5];
            empArray = new Employee[3];

            // populate the array
            for (int i = 0;i<empArray.Length;i++)
            {
                empArray[i] = new Employee(i+5);
            }
           
        }

        [STAThread]
        static void Main()
        {
            Tester t = new Tester();
            t.Run();
        }
    }
}

Example 15-1 begins by creating a simple Employee class to add to the array. When Run() begins, two arrays are declared, one of type int and the other of type Employee:

int[] intArray;
Employee[] empArray;

The int array is populated with ints set to zero. The empArray is initialized with null references.

The array does not have Employee objects whose member fields are set to null; it does not have Employee objects at all. What is in the cubby holes of the array is just nulls. Nothing. Nada. When you create the Employee objects, you can then store them in the array.

You must populate the Employee array before you can refer to its elements:

for (int i = 0;i<empArray.Length;i++)
{
    empArray[i] = new Employee(i+5);
}

The exercise has no output. You've added the elements to the array, but how do you use them? How do you refer to them?

15.1.3 Accessing Array Elements

Arrays are zero-based, which means that the index of the first element is always zero — in this case, myArray[0]. The second element is element 1. The numeric value is called the index, or the offset. Index 3 indicates the element offset from the beginning of the array by 3 elements, that is, the fourth element in the array. You access element 3 using the index operator []. Thus, the fourth element is returned by writing:

myArray[3]; // return the 4th element (at offset 3)

Because arrays are objects, they have properties. One of the more useful properties of the Array class is Length, which tells you how many objects are in an array. Array objects can be indexed from 0 to Length-1. That is, if there are five elements in an array, their indices are 0,1,2,3,4.

In Example 15-1, you created an array of Employees and an array of integers. In Example 15-2, you return to that array and access the elements. You'll override ToString() in the Employee class so that Employee objects can display their value.

Example 15-2. Accessing two simple arrays
using System;

namespace ArrayDemo
{

    // a simple class to store in the array
    public class Employee
    {
        private int empID;

        // constructor
        public Employee(int empID)
        {
            this.empID = empID;
        }
        public override string ToString()
        {
            return empID.ToString();
        }
    }

   class Tester
   {
      public void Run()
      {
          int[] intArray;
          Employee[] empArray;
          intArray = new int[5];
          empArray = new Employee[3];

          // populate the array
          for (int i = 0;i<empArray.Length;i++)
          {
              empArray[i] = new Employee(i+5);
          }
           
          Console.WriteLine("The int array...");
          for (int i = 0;i<intArray.Length;i++)
          {
              Console.WriteLine(intArray[i].ToString());
          }

          Console.WriteLine("\nThe employee array...");
          for (int i = 0;i<empArray.Length;i++)
          {
              Console.WriteLine(empArray[i].ToString());
          }
      }

      [STAThread]
      static void Main()
      {
         Tester t = new Tester();
         t.Run();
      }
   }
}

Output:
The int array...
0
0
0
0
0

The employee array...
5
6
7

In this version of the example, the contents of the arrays are printed to ensure that they are filled as intended. Notice that you refer to the individual members of the array using the index operator.

The five integers print their value first, followed by the three Employee objects.

If you comment out the code in which the Employee objects are created, you'll generate an exception when you try to display the contents of the Employee array. This demonstrates that arrays of reference types are initialized with null references.

15.1.4 The foreach Statement

The foreach looping 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

The foreach statement creates a new object that holds a reference to each of the objects in the collection, in turn, as you loop through the collection. For example, you might write:

foreach ( int theInt in intArray )

Each time through the loop, the next member of intArray is assigned to the integer variable theInt. You can then use that object to display the value, as in:

Console.WriteLine(theInt.ToString());

Similarly, you might iterate through the Employee array:

foreach ( Employee e in empArray )

In the case shown here, e is an object of type Employee. For each turn through the loop, e refers to the next Employee in the array:

Console.WriteLine(e.ToString());

Example 15-3 rewrites the Run() method of Example 15-2 to use a foreach loop rather than a for loop, but is otherwise unchanged.

Example 15-3. Using a foreach loop
public void Run()
{
    int[] intArray;
    Employee[] empArray;
    intArray = new int[5];
    empArray = new Employee[3];

    // populate the array
    for (int i = 0;i<empArray.Length;i++)
    {
        empArray[i] = new Employee(i+5);
    }
     
    Console.WriteLine("The int array...");
    foreach( int theInt in intArray ) 
    {
        Console.WriteLine(theInt.ToString());
    }

    Console.WriteLine("\nThe employee array...");
    foreach ( Employee e in empArray ) 
    {
        Console.WriteLine(e.ToString());
    }
}

 Output:
The int array...
0
0
0
0
0

The employee array...
5
6
7

The output for Example 15-3 is identical to Example 15-2. However, rather than creating a for statement that measures the size of the array and uses a temporary counting variable as an index into the array, as in Example 15-2:

for (int i = 0; i < empArray.Length; i++)
{
    Console.WriteLine(empArray[i].ToString());
}

Example 15-3 iterates over the array with the foreach loop, which automatically extracts the next item from within the array and assigns it to a temporary object. In the following case, the temporary object is of type Employee (it is a reference to an Employee object) and is named e.

foreach (Employee e in empArray)
{
   Console.WriteLine(e.ToString()); 
}

Since e is a reference to an Employee, you can call any public method of Employee. In the case shown, you call the ToString() method of Employee, which Employee inherits from Object and overrides to display meaningful information about the Employee object.

15.1.5 Initializing Array Elements

Rather than assigning elements to the array as you have done so far, it is possible to initialize the contents of an array at the time it is instantiated by providing a list of values delimited by curly braces ({}). C# provides two different syntaxes to accomplish the same task:

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

There is no practical difference between these two statements, and most programmers will use the shorter syntax because we are, by nature, lazy. We are so lazy, we'll work all day to save a few minutes doing a task — which isn't so crazy if we're going to do that task hundreds of times! Example 15-4 again rewrites the Run() method of Example 15-1, this time demonstrating initialization of both arrays.

Example 15-4. Initializing array elements
public void Run()
{
    int[] intArray = { 2, 4, 6, 8, 10 };
    Employee[] empArray = 
       { new Employee(5), new Employee(7), new Employee(9) };

     
    Console.WriteLine("The int array...");
    foreach( int theInt in intArray )
    {
        Console.WriteLine(theInt.ToString());
    }

    Console.WriteLine("\nThe employee array...");
    foreach ( Employee e in empArray )
    {
        Console.WriteLine(e.ToString());
    }
}

Output:
The int array...
2
4
6
8
10

The employee array...
5
7
9

In Example 15-3, you created an Employee array by first allocating space for the array (which was filled with nulls).

empArray = new Employee[3];

You then filled the array by writing a for loop and adding a value for each member:

// populate the array
for (int i = 0;i<empArray.Length;i++)
{
    empArray[i] = new Employee(i+5);
}

In Example 15-4, you initialized the array by providing the values at the time you instantiated the array:

Employee[] empArray = 
   { new Employee(5), new Employee(7), new Employee(9) };

This combines the steps of defining the array and populating the array.

15.1.6 The params Keyword

C# provides the keyword params.

public void DisplayVals(params int[] intVals)

This keyword indicates that the method treats all the parameters passed to it as an array, even if you pass in the parameters as individual values. This helps you solve the thorny programming problem of how you can define a method that takes a variable number of parameters (that is, when you can't know at compile time how many parameters you'll be passing into the method).

As far as the client (the calling method) is concerned, you pass in a variable number of parameters.

myObject.DisplayVals(5,7,9);         // display 3 values
myObject.DisplayVals(5,7,9,11,13);   // display 5 values

The implementing method (DisplayVals) treats the variable number of parameters as an array and can just iterate through the array to find each parameter!

public void DisplayVals(params int[] intVals)
{
    foreach (int i in intVals)
    {
        Console.WriteLine("DisplayVals {0}",i);
    }
}

In effect, even though you invoked DisplayVals( ) with this code:

myObject.DisplayVals(5,7,9,11,13);

the DisplayVals( ) method treats the invocation exactly as if you had written:

int [] explicitArray = new int[5] {5,7,9,11,13};
DisplayVals(explicitArray);

In fact, you can call that same DisplayVals( ) method and pass in an actual array. You are free to call it either way: with individual parameters that are treated as an array, or with an array. Example 15-5 demonstrates how to use the params keyword.

Example 15-5. The params keyword
using System;

namespace ParamsDemo
{
   class Tester
   {
      public void Run()
      {
          int a = 5;
          int b = 6;
          int c = 7;
          Console.WriteLine("Calling with three integers");
          DisplayVals(a,b,c);

          Console.WriteLine("\nCalling with four integers");
          DisplayVals(5,6,7,8);

          Console.WriteLine("\ncalling with an array of four integers");
          int [] explicitArray = new int[4] {5,6,7,8};
          DisplayVals(explicitArray);
      }

       // takes a variable number of integers
       public void DisplayVals(params int[] intVals)
       {
           foreach (int i in intVals)
           {
               Console.WriteLine("DisplayVals {0}",i);
           }
       }

      [STAThread]
      static void Main()
      {
         Tester t = new Tester();
         t.Run();
      }
   }
}

Output:
Calling with three integers
DisplayVals 5
DisplayVals 6
DisplayVals 7

Calling with four integers
DisplayVals 5
DisplayVals 6
DisplayVals 7
DisplayVals 8

calling with an array of four integers
DisplayVals 5
DisplayVals 6
DisplayVals 7
DisplayVals 8

The first time you call DisplayVals(), you pass in three integer variables:

int a = 5;
int b = 6;
int c = 7;
DisplayVals(a,b,c);

The second time you call DisplayVals(), you pass in four literal constants:

DisplayVals(5,6,7,8);

In both cases, DisplayVals() treats the parameters as if they were declared in an array. In the final invocation, you explicitly create an array and pass that as the parameter to the method:

int [] explicitArray = new int[4] {5,6,7,8};
DisplayVals(explicitArray);

Thus, you can call DisplayVals() by passing in any number of variables (a,b,c), by passing in any number of literal constants (5,6,7,8), or by passing in an array (explicitArray) with any number of members.

    Team LiB   Previous Section   Next Section