One-Dimensional and Rectangular Arrays
Instantiating a One-Dimensional or Rectangular Array
Comparing Rectangular and Jagged Arrays
Useful Inherited Array Members
An array is a set of uniform data elements, represented by a single variable name. The individual elements are accessed using the variable name together with one or more indexes between square brackets, as shown here:
Let's start with some important definitions having to do with arrays in C#.
The following are some important general facts about C# arrays:
Figure 14-1. Dimensions and sizes
C# provides two kinds of arrays:
Additionally, there are two types of multidimensional arrays, rectangular arrays and jagged arrays, which have the following characteristics:
int x = myArray2[4, 6, 1] // One set of square brackets
jagArray1[2][7][4] // Three sets of square brackets
Figure 14-2 shows the kinds of arrays available in C#.
Figure 14-2. One-dimensional, rectangular, and jagged arrays
An array instance is an object whose type derives from class System.Array
. Since arrays are derived from this BCL base class, they inherit a number of useful members from it, such as the following:
Rank
: A property that returns the number of dimensions of the arrayLength
: A property that returns the length (the total number of elements)of the arrayArrays are reference types, and as with all reference types, they have both a reference to the data and the data object itself. The reference is in either the stack or the heap, and the data object itself will always be in the heap. Figure 14-3 shows the memory configuration and components of an array.
Figure 14-3. Structure of an array
Although an array is always a reference type, the elements of the array can be either value types or reference types.
Figure 14-4 shows a value type array and a reference type array.
Figure 14-4. Elements can be values or references.
Syntactically, one-dimensional arrays and rectangular arrays are very similar, so I'll treat them together. I'll then treat jagged arrays separately.
To declare a one-dimensional or rectangular array, use a single set of square brackets between the type and the variable name.
The rank specifiers are commas between the brackets. They specify the number of dimensions the array will have. The rank is the number of commas, plus one. For example, no commas indicates a one-dimensional array, one comma indicates a two-dimensional array, and so forth.
The base type, together with the rank specifiers, is the type of the array. For example, the following line of code declares a one-dimensional array of long
s. The type of the array is long[]
, which is read as “an array of longs.”
The following code shows examples of declarations of rectangular arrays. Notice the following:
Note Unlike C/C++, the brackets follow the base type, not the variable name.
To instantiate an array, you use an array-creation expression. An array-creation expression consists of the new
operator, followed by the base type, followed by a pair of square brackets. The length of each dimension is placed in a comma-separated list between the brackets.
The following are examples of one-dimensional array declarations:
arr2
is a one-dimensional array of four int
s.mcArr
is a one-dimensional array of four MyClass
references.The following is an example of a rectangular array. Array arr3
is a three-dimensional array.
Figure 14-5. Declaring and instantiating arrays
Note Unlike object-creation expressions, array-creation expressions do not contain parentheses—even for reference type arrays.
An array element is accessed using an integer value as an index into the array.
The following code shows examples of declaring, writing to, and reading from a one-dimensional and a two-dimensional array:
int[] intArr1 = new int[15]; // Declare 1-D array.
intArr1[2] = 10; // Write to element 2 of the array.
int var1 = intArr1[2]; // Read from element 2 of the array.
int[,] intArr2 = new int[5,10]; // Declare 2-D array.
intArr2[2,3] = 7; // Write to the array.
int var2 = intArr2[2,3]; // Read from the array.
The following code shows the full process of creating and accessing a one-dimensional array:
int[] myIntArray; // Declare the array.
myIntArray = new int[4]; // Instantiate the array.
for( int i=0; i<4; i++ ) // Set the values.
myIntArray[i] = i*10;
// Read and display the values of each element.
for( int i=0; i<4; i++ )
Console.WriteLine("Value of element {0} = {1}", i, myIntArray[i]);
This code produces the following output:
Value of element 0 is 0
Value of element 1 is 10
Value of element 2 is 20
Value of element 3 is 30
Whenever an array is created, each of the elements is automatically initialized to the default value for the type. The default values for the predefined types are 0
for integer types, 0.0
for floating-point types, false
for Booleans, and null
for reference types.
For example, the following code creates an array and initializes its four elements to the value 0
. Figure 14-6 illustrates the layout in memory.
int[] intArr = new int[4];
Figure 14-6. Automatic initialization of a one-dimensional array
For a one-dimensional array, you can set explicit initial values by including an initialization list immediately after the array-creation expression of an array instantiation.
For example, the following code creates an array and initializes its four elements to the values between the curly braces. Figure 14-7 illustrates the layout in memory.
Figure 14-7. Explicit initialization of a one-dimensional array
To explicitly initialize a rectangular array, you need to follow these rules:
For example, the following code shows the declaration of a two-dimensional array with an initialization list. Figure 14-8 illustrates the layout in memory.
Figure 14-8. Initializing a rectangular array
Rectangular arrays are initialized with nested, comma-separated initialization lists. The initialization lists are nested in curly braces. This can sometimes be confusing, so to get the nesting, grouping, and commas right, the following tips might be helpful:
For example, read the following declaration as “intArray
has four groups of three groups of two elements.”
When combining declaration, array creation, and initialization in a single statement, you can omit the array-creation expression part of the syntax entirely and provide just the initialization portion. Figure 14-9 shows this shortcut syntax.
Figure 14-9. Shortcut for array declaration, creation, and initialization
So far, we've explicitly specified the array types at the beginnings of all our array declarations. But, like other local variables, your arrays can also be implicitly typed. This means the following:
var
instead of the array type.The following code shows explicit and implicit versions of three array declarations. The first set is a one-dimensional array of int
s. The second is a two-dimensional array of int
s. The third is an array of strings. Notice that in the declaration of implicitly typed intArr4
you still need to include the rank specifier in the initialization.
The following code puts together all the pieces we've looked at so far. It creates, initializes, and uses a rectangular array.
// Declare, create, and initialize an implicitly typed array.
var arr = new int[,] {{0, 1, 2}, {10, 11, 12}};
// Print the values.
for( int i=0; i<2; i++ )
for( int j=0; j<3; j++ )
Console.WriteLine("Element [{0},{1}] is {2}", i, j, arr[i,j]);
This code produces the following output:
Element [0,0] is 0
Element [0,1] is 1
Element [0,2] is 2
Element [1,0] is 10
Element [1,1] is 11
Element [1,2] is 12
A jagged array is an array of arrays. Unlike rectangular arrays, the subarrays of a jagged array can have different numbers of elements.
For example, the following code declares a two-dimensional jagged array. Figure 14-10 shows the array's layout in memory.
jagArr
is an array of three arrays of int
s.”int[][] jagArr = new int[3][]; // Declare and create top-level array.
... // Declare and create subarrays.
Figure 14-10. A jagged array is an array of arrays.
The declaration syntax for jagged arrays requires a separate set of square brackets for each dimension. The number of sets of square brackets in the declaration of the array variable determines the rank of the array.
You can combine the jagged array declaration with the creation of the first-level array using an array-creation expression, such as in the following declaration. Figure 14-11 shows the result.
Figure 14-11. Shortcut first-level instantiation
You cannot instantiate more than the first-level array in the declaration statement.
Unlike other types of arrays, you cannot fully instantiate a jagged array in a single step. Since a jagged array is an array of independent arrays, each array must be created separately. Instantiating a full jagged array requires the following steps:
For example, the following code shows the declaration, instantiation, and initialization of a two-dimensional jagged array. Notice in the code that the reference to each subarray is assigned to an element in the top-level array. Steps 1 through 4 in the code correspond to the numbered representations in Figure 14-12.
int[][] Arr = new int[3][]; // 1. Instantiate top level
Arr[0] = new int[] {10, 20, 30}; // 2. Instantiate subarray
Arr[1] = new int[] {40, 50, 60, 70}; // 3. Instantiate subarray
Arr[2] = new int[] {80, 90, 100, 110, 120}; // 4. Instantiate subarray
Figure 14-12. Creating a two-dimensional jagged array
Since the subarrays in a jagged array are themselves arrays, It's possible to have rectangular arrays inside jagged arrays. For example, the following code creates a jagged array of three two-dimensional rectangular arrays and initializes them with values. It then displays the values.
GetLength(int n)
method of arrays, inherited from System.Array
, to get the length of the specified dimension of the array.Figure 14-13. Jagged array of three two-dimensional arrays
The structure of rectangular and jagged arrays is significantly different. For example, Figure 14-14 shows the structure of a rectangular three-by-three array, as well as a jagged array of three one-dimensional arrays of length 3.
Figure 14-14. Comparing the structure of rectangular and jagged arrays
One-dimensional arrays have specific instructions in the CIL that allow them to be optimized for performance. Rectangular arrays do not have these instructions and are not optimized to the same level. Because of this, it can sometimes be more efficient to use jagged arrays of one-dimensional arrays—which can be optimized—than rectangular arrays, which cannot.
On the other hand, the programming complexity can be less for a rectangular array because it can be treated as a single unit, rather than an array of arrays.
The foreach
statement allows you to sequentially access each element in an array. It's actually a more general construct in that it also works with other collection types as well—but this section only discusses its use with arrays. Chapter 20 covers its use with other collection types.
The important points of the foreach
statement are the following:
foreach
statement uses the iteration variable to sequentially represent each element in the array.foreach
statement is shown here, where
In the following text, I'll sometimes use implicit typing, and other times I'll use explicit typing so that you can see the exact type being used. But the forms are semantically equivalent.
The foreach
statement works in the following way:
foreach
statement selects the next element in the array and repeats the process.In this way, it cycles through the array, allowing you to access each element one by one. For example, the following code shows the use of a foreach
statement with a one-dimensional array of four integers:
WriteLine
statement, which is the body of the foreach
statement, is executed once for each of the elements of the array.item
has the value of the first element of the array. Each successive time, it has the value of the next element in the array.This code produces the following output:
Item Value: 10
Item Value: 11
Item Value: 12
Item Value: 13
Since the value of the iteration variable is read-only, clearly it cannot be changed. But this has different effects on value type arrays and reference type arrays.
For value type arrays, this means you cannot change the data of the array. For example, in the following code, the attempt to change the data in the iteration variable produces a compile-time error message:
int[] arr1 = {10, 11, 12, 13};
foreach( int item in arr1 )
item++; // Compilation error. Changing variable value is not allowed.
For reference type arrays, you still cannot change the iteration variable, but the iteration variable only holds the reference to the data, not the data itself. So although you cannot change the reference, you can change the data through the iteration variable.
The following code creates an array of four MyClass
objects and initializes them. In the first foreach
statement, the data in each of the objects is changed. In the second foreach
statement, the changed data is read from the objects.
class MyClass
{
public int MyField = 0;
}
class Program {
static void Main() {
MyClass[] mcArray = new MyClass[4]; // Create array
for (int i = 0; i < 4; i++)
{
mcArray[i] = new MyClass(); // Create class objects
mcArray[i].MyField = i; // Set field
}
foreach (MyClass item in mcArray)
item.MyField += 10; // Change the data.
foreach (MyClass item in mcArray)
Console.WriteLine("{0}", item.MyField); // Read the changed data.
}
}
This code produces the following output:
10
11
12
13
In a multidimensional array, the elements are processed in the order in which the rightmost index is incremented fastest. When the index has gone from 0 to length – 1, the next index to the left is incremented, and the indexes to the right are reset to 0.
The following example shows the foreach
statement used with a rectangular array:
class Program
{
static void Main()
{
int total = 0;
int[,] arr1 = { {10, 11}, {12, 13} };
foreach( var element in arr1 )
{
total += element;
Console.WriteLine
("Element: {0}, Current Total: {1}", element, total);
}
}
}
This code produces the following output:
Element: 10, Current Total: 10
Element: 11, Current Total: 21
Element: 12, Current Total: 33
Element: 13, Current Total: 46
Since jagged arrays are arrays of arrays, you must use separate foreach
statements for each dimension in the jagged array. The foreach
statements must be nested properly to make sure that each nested array is processed properly.
For example, in the following code, the first foreach
statement cycles through the top-level array—arr1
—selecting the next subarray to process. The inner foreach
statement processes the elements of that subarray.
class Program
{
static void Main( )
{
int total = 0;
int[][] arr1 = new int[2][];
arr1[0] = new int[] { 10, 11 };
arr1[1] = new int[] { 12, 13, 14 };
foreach (int[] array in arr1) // Process the top level.
{
Console.WriteLine("Starting new array");
foreach (int item in array) // Process the second level.
{
total += item;
Console.WriteLine(" Item: {0}, Current Total: {1}", item, total);
}
}
}
}
This code produces the following output:
Starting new array
Item: 10, Current Total: 10
Item: 11, Current Total: 21
Starting new array
Item: 12, Current Total: 33
Item: 13, Current Total: 46
Item: 14, Current Total: 60
Under certain conditions, you can assign an object to an array element even if the object is not of the array's base type. This property of arrays is called array covariance. You can use array covariance if the following are true:
Since there is always an implicit conversion between a derived class and its base class, you can always assign an object of a derived class to an array declared for the base class.
For example, the following code declares two classes, A
and B
, where class B
derives from class A
. The last line shows covariance by assigning objects of type B
to array elements of type A
. Figure 14-15 shows the memory layout for the code.
class A { ... } // Base class
class B : A { ... } // Derived class
class Program {
static void Main() {
// Two arrays of type A[]
A[] AArray1 = new A[3];
A[] AArray2 = new A[3];
// Normal--assigning objects of type A to an array of type A
AArray1[0] = new A(); AArray1[1] = new A(); AArray1[2] = new A();
// Covariant--assigning objects of type B to an array of type A
AArray2[0] = new B(); AArray2[1] = new B(); AArray2[2] = new B();
}
}
Figure 14-15. Arrays showing covariance
Note There is no covariance for value type arrays.
I mentioned earlier that C# arrays are derived from class System.Array
. From that base class they inherit a number of useful properties and methods. Table 14-1 lists some of the most useful ones.
Table 14-1. Some Useful Members Inherited by Arrays
Member | Type | Lifetime | Meaning |
Rank |
Property | Instance | Gets the number of dimensions of the array |
Length |
Property | Instance | Gets the total number of elements in all the dimensions of the array |
GetLength |
Method | Instance | Returns the length of a particular dimension of the array |
Clear |
Method | Static | Sets a range of elements to 0 or null |
Sort |
Method | Static | Sorts the elements in a one-dimensional array |
BinarySearch |
Method | Static | Searches a one-dimensional array for a value, using binary search |
Clone |
Method | Instance | Performs a shallow copy of the array—copying only the elements, both for arrays of value types and reference types |
IndexOf |
Method | Static | Returns the index of the first occurrence of a value in a one-dimensional array |
Reverse |
Method | Static | Reverses the order of the elements of a range of a one-dimensional array |
GetUpperBound |
Method | Instance | Gets the upper bound at the specified dimension |
For example, the following code uses some of these properties and methods:
public static void PrintArray(int[] a)
{
foreach (var x in a)
Console.Write("{0} ", x);
Console.WriteLine("");
}
static void Main()
{
int[] arr = new int[] { 15, 20, 5, 25, 10 };
PrintArray(arr);
Array.Sort(arr);
PrintArray(arr);
Array.Reverse(arr);
PrintArray(arr);
Console.WriteLine();
Console.WriteLine("Rank = {0}, Length = {1}",arr.Rank, arr.Length);
Console.WriteLine("GetLength(0) = {0}",arr.GetLength(0));
Console.WriteLine("GetType() = {0}",arr.GetType());
}
This code produces the following output:
15 20 5 25 10
5 10 15 20 25
25 20 15 10 5
Rank = 1, Length = 5
GetLength(0) = 5
GetType() = System.Int32[]
The Clone
method performs a shallow copy of an array. This means that it only creates a clone of the array itself. If it is a reference type array, it does not copy the objects referenced by the elements. This has different results for value type arrays and reference type arrays.
The Clone
method returns a reference of type object
, which must be cast to the array type.
For example, the following code shows an example of cloning a value type array, producing two independent arrays. Figure 14-16 illustrates the steps shown in the code.
static void Main()
{
int[] intArr1 = { 1, 2, 3 }; // Step 1
int[] intArr2 = (int[]) intArr1.Clone(); // Step 2
intArr2[0] = 100; intArr2[1] = 200; intArr2[2] = 300; // Step 3
}
Figure 14-16. Cloning a value type array produces two independent arrays.
Cloning a reference type array results in two arrays pointing at the same objects. The following code shows an example. Figure 14-17 illustrates the steps shown in the code.
class A
{
public int Value = 5;
}
class Program
{
static void Main()
{
A[] AArray1 = new A[3] { new A(), new A(), new A() }; // Step 1
A[] AArray2 = (A[]) AArray1.Clone(); // Step 2
AArray2[0].Value = 100;
AArray2[1].Value = 200;
AArray2[2].Value = 300; // Step 3
}
}
Figure 14-17. Cloning a reference type array produces two arrays referencing the same objects.
Table 14-2 summarizes some of the important similarities and differences between the three types of arrays.
3.138.122.11