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. In C# 8.0, there is an “index from end” operator. For example, an index value of ^1 would access the last element in the array.

For beginners, it is helpful sometimes to think of the index as an offset. The first item is zero away from the start of the array. The second item is one away from the start of the array—and so on.

Arrays are a fundamental part of nearly every programming language, so they are required learning for virtually all developers. Although arrays are frequently used in C# programming, and necessary for the beginner to understand, most C# programs now use generic collection types rather than arrays when storing collections of data. Therefore, readers should skim over the following section, “Declaring an Array,” simply to become familiar with their instantiation and assignment. Table 3.2 provides the highlights of what to note. Generic collections are covered in detail in Chapter 15.

Table 3.2: Array Highlights

Description

Example

Declaration

Note that the brackets appear with the data type.

Multidimensional arrays are declared using commas, where the comma+1 specifies the number of dimensions.

// 1.

string[] languages; // one-dimensional

int[,] cells; // two-dimensional

Assignment

The new keyword is optional but only at declaration time.

If not assigned during declarations, the new keyword is required when instantiating an array.

Specifying the data type is optional.6 However, without the data type, the size cannot be specified.

Both the size and the values do not have to be literals; they can be determined at runtime.

If both values and size are specified, the quantities must be consistent.

Arrays can be assigned without values. As a result, the value of each item in the array is initialized to its default.

The data type and the size are required if no values are specified.

// 2.

string[] languages = {

     "C#", "COBOL", "Java",

     "C++", "TypeScript", "Pascal",

     "Python", "Lisp", "JavaScript"};

languages = new string[]{

     "C#", "COBOL", "Java",

     "C++", "TypeScript", "Pascal",

     "Python", "Lisp", "JavaScript" };

// Multidimensional array assignment

// and initialization

int[,] cells = new [,]

  {

       {1, 0, 2},

       {1, 2, 0},

       {1, 2, 1}

   };

languages = new string[9];

Forward Accessing an Array

Arrays are zero based, so the first element in an array is at index 0.

The square brackets are used to store and retrieve data from an array.

// 3.

string[] languages = new []{

     "C#", "COBOL", "Java",

     "C++", "TypeScript", "Visual Basic",

     "Python", "Lisp", "JavaScript"};

// Retrieve fifth item in languages array (TypeScript)

string language = languages[4];

// Write “TypeScript”

Console.WriteLine(language);

// Retrieve second item from the end (Python)

language = languages[^3];

// Write “Python”

Console.WriteLine(language);

Reverse Accessing an Array

Starting in C# 8.0, you can also index an array from the end. For example, item ^1 corresponds to indexing the last element of the array and ^3 corresponds to indexing the third-from-the-last element.

Ranges

C# 8.0 allows you to identify and extract an array of elements using the range operator, which identifies the starting item up to but excluding the end item.

Console.WriteLine($@"^3..^0: {

     // Python, Lisp, JavaScript

     string.Join(", ", languages[^3..^0])

}");

Console.WriteLine($@"^3..: {

     // Python, Lisp, JavaScript

     string.Join(", ", languages[^3..])

}");

Console.WriteLine($@" 3..^3: {

     // C++, TypeScript, Visual Basic

     string.Join(", ", languages[3..^3])

}");

Console.WriteLine($@" ..^6: {

     // C#, COBOL, Java

     string.Join(", ", languages[..^6])

}");

In addition, the final section of this chapter, “Common Array Errors,” provides a review of some of the array idiosyncrasies.

Declaring an Array

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

Listing 3.6: 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 1. These two pieces form the data type for the variable languages.

Language Contrast: C++ and Java—Array Declaration

The square brackets for an array in C# appear immediately following the data type instead of after the variable declaration. This practice keeps all the type information together instead of splitting it up both before and after the identifier, as occurs in C++ and Java—with the latter also allowing the square brackets to appear after either the data type or the variable name.

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

Listing 3.7: Declaring a Two-Dimensional Array
//    |   | 
// ---+---+--- 
//    |   | 
// ---+---+---
//    |   | 
int[,] cells;

In Listing 3.7, the array has a rank of 2. The first dimension could correspond to cells going across and the second dimension to 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 3.8 declares an array of strings and then assigns the names of nine languages within curly braces.

Listing 3.8: Array Declaration with Assignment
string[] languages = { "C#""COBOL""Java",
    "C++""TypeScript""Visual Basic",
    "Python""Lisp""JavaScript" };

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 3.8 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, as shown in Listing 3.9.

Listing 3.9: Array Assignment Following Declaration
string[] languages;
languages = new string[]{ "C#""COBOL""Java",
    "C++""TypeScript""Visual Basic",
    "Python""Lisp""JavaScript" };

Specifying the data type of the array (string) following new is optional7 as long as the compiler is able to deduce the element type of the array from the types of the elements in the array initializer. The square brackets are still required.

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

Listing 3.10: Array Assignment with new during Declaration
string[] languages = new string[]{
    "C#""COBOL""Java",
    "C++""TypeScript""Visual Basic",
    "Python""Lisp""JavaScript" };

The use of the new keyword tells the runtime to allocate memory for the data type. It instructs the runtime to instantiate the data type—in 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 3.11 demonstrates this syntax.

Listing 3.11: Declaration and Assignment with the new Keyword
string[] languages = new string[9]{
    "C#""COBOL""Java",
    "C++""TypeScript""Visual Basic",
    "Python""Lisp""JavaScript" };

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

Listing 3.12: Assigning without Literal Values
string[] languages = new string[9];

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

Reference types—whether nullable or not (such as string and string?)—are initialized to null.
Nullable value types are all initialized to null.
Non-nullable numeric types are initialized to 0.
bool is initialized to false.
char is initialized to \0.

Nonprimitive value types are recursively initialized by initializing each of their fields to their default values. As a result, it is not necessary to individually assign each element of an array before using it.

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 3.13 creates an array based on the size specified in the Console.ReadLine() call.

Listing 3.13: Defining the Array Size at Runtime
string[] groceryList;
Console.Write("How many items on the list? ");
int size = int.Parse(Console.ReadLine());
groceryList = new string[size];
// ...

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

Listing 3.14: Declaring a Two-Dimensional Array
int[,] cells = new int[3,3];

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

Listing 3.15: 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 3. Note that the sizes of each int[] element must all be identical. The declaration shown in Listing 3.16, therefore, is not valid.

Listing 3.16: A Multidimensional Array with Inconsistent Size, Causing an Error
// ERROR: Each dimension must be consistently sized
// ...

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

Listing 3.17: Initializing a Three-Dimensional Array
bool[, ,] cells;
cells = new bool[2, 3, 3]
{
    // Player 1 moves
    //  X |   |
    // ---+---+---
    //  X |   | 
    // ---+---+---
    //  X |   | X 
    {
        {truefalsefalse},
        {truefalsefalse},
        {truefalsetrue}
    },
    // Player 2 moves
    //    |   | O
    // ---+---+---
    //    | O | 
    // ---+---+---
    //    | O | 
    {
        {falsefalsetrue},
        {falsetruefalse},
        {falsetruetrue}
    }
};

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 3 × 3. 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; furthermore, jagged arrays do not need to be consistently sized. Therefore, it is possible to initialize a jagged array as shown in Listing 3.18.

Listing 3.18: Initializing a Jagged Array
int[][] cells = {
    new [] {1, 0, 2, 0},
    new [] {1, 2, 0},
    new [] {1, 2},
    new [] {1}
};

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

Notice that a jagged array requires an array instance (or null) for each internal array. In the preceding example, you use new to instantiate the internal element of the jagged arrays. Leaving out the instantiation would cause a compile error. (As noted, specifying the data type is optional when values are provided.)

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 3.19, 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.

Listing 3.19: Declaring and Accessing an Array
string[] languages = new []{
    "C#""COBOL""Java",
    "C++""TypeScript""Visual Basic",
    "Python""Lisp""JavaScript"};
    // Retrieve fifth item in languages array (TypeScript)
    string language = languages[4];
// Write "TypeScript"
Console.WriteLine(language);
    // Retrieve second item from the end (Python)
    language = languages[^3];
// Write "Python"
Console.WriteLine(language);

Starting with C# 8.0, you can also access items relative to the end of the array using the index from end operator, also known as the ^ (hat) operator. Indexing with ^1, for example, retrieves the last item in the array, whereas the first element using the index from end operator would be ^9 (where 9 is the number of items in the array). In Listing 3.19, ^3 specifies that the value stored in the third item from the end ("Python") in the languages variable is assigned to language.

Since item ^1 is the last element, item ^0 corresponds to one past the end of the list. Of course, there is no such element, so you can’t successfully index the array using ^0. Similarly, using the length of the array, 9 in this case, refers to an element off the end of the array. In addition, you can’t use negative values when indexing into an array.

There is seemingly a discrepancy between indexing from the beginning of the array with a positive integer and indexing from the end of the array using a ^ integer value (or an expression that returns an integer value). The first begins counting at 0 to access the first element, while the second starts at ^1 to access the last element. The C# design team chose to index from the beginning using 0 for consistency with other languages on which C# is based (C, C++, and Java, for example). When indexing from the end, C# followed Python’s precedence (since the C-based languages didn’t support an index from end operator) and started counting from the end with 1 as well. Unlike in Python, however, the C# team selected the ^ operator (rather than negative integers) to ensure there was no backward incompatibility when using the index operator on collection types (not arrays) that allow negative values. (The ^ operator has additional advantages when supporting ranges, as discussed later in the chapter.)

Language Contrast: C++/Java/JavaScript/Python—Array Indexing

Like its heritage (C++/Java/JavaScript/Python), C# indexes arrays from 0. While most of these same predecessors don’t index from the end, Python does so. Like Python, C#’s indexing from the end starts with 1; albeit, the indexing from the end operator is ^ rather than -.

One way to remember how the index from end operator works is to notice that when indexing from the end with a positive integer, the value begins with length –1 for the last element, length –2 for the second-to-last element, and so on. The integer subtracted from the length corresponds to the “last from end” index value—^1, ^2, and so on. Furthermore, with this approach the value of the index from the beginning of the array plus the value of the index from the end of the array always totals the length of the array.

Note that the index from end operator is not limited to a literal integer value. You can also use expressions such as

languages[^languages.Length]

which will return the first item.

note
In C#, an index begins counting at 0 to access the first element, while the index from the end index starts at ^1 to access the last element. When indexing from the end, using the ^ operator, the value begins with length—1. The integer subtracted from the length corresponds to the “last from end” index value. Thus, the value of the index from the beginning of the array plus the value of the index from the end of the array always totals the length of the array.

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

Listing 3.20: Swapping Data between Positions in an Array
string[] languages = new [] {
    "C#""COBOL""Java",
    "C++""TypeScript""Visual Basic",
    "Python""Lisp""JavaScript" };
// 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 3.21.

Listing 3.21: 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 3.22).

Listing 3.22: Declaring a Jagged Array
int[][] cells = {
    new []{1, 0, 2},
    new []{0, 2, 0},
    new []{1, 2, 1}
};
 
cells[1][0] = 1;
// ...

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

Listing 3.23: Retrieving the Length of an Array
Console.WriteLine(
    $"There are {languages.Length} languages in the array.");

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 when you attempt to access (either retrieve or assign) 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 3.24.

Listing 3.24: 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 runtime allowable index for the languages variable is 8, because that is how far it is from the start.
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 vulnerability called a buffer overrun. In contrast, the Common Language Runtime protects all C# (and Managed C++) code from overstepping array bounds, virtually eliminating the possibility of a buffer overrun issue in managed code.

With C# 8.0, the same problem occurs when accessing the ^0 item: Since ^1 is the last item, ^0 is one past the end of the array—and there is no such item.

To avoid overstepping the bounds on an array when accessing the last element, use a length check to verify that the array has a length greater than 0, and use ^1 (starting with C# 8.0) or Length – 1 in place of a hardcoded value when accessing the last item in the array. To use Length as an index, it is necessary to subtract 1 to avoid an out-of-bounds error (see Listing 3.25).

Listing 3.25: Using Length - 1 in the Array Index
string[] languages = new string[9];
// ...
languages[4] = languages[languages.Length - 1];

(Of course, first check for null before accessing the array if there is a chance there is no array instance in the first place.)

Guidelines
CONSIDER checking the array length before indexing into an array rather than assuming the length.
CONSIDER using the index from end operator (^) rather than Length - 1 with C# 8.0 or higher.

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

For a jagged array, Length returns the number of elements in the first array. Because a jagged array is an array of arrays, Length evaluates only the outside containing array and returns its element count, regardless of what is inside the internal arrays.

Ranges

Another indexing-related feature added to C# 8.0 is support for array slicing—that is, extracting a slice of an array into a new array. The syntax for specifying a range is .., the range operator, which may optionally be placed between indices (including the indices from the end). Listing 3.26 provides examples.

Listing 3.26: Examples of the Range Operator
string[] languages = new [] {
    "C#""COBOL""Java",
    "C++""TypeScript""Swift",
    "Python""Lisp""JavaScript"};
 
Console.WriteLine($@"  0..3: {
    string.Join(", ", languages[0..3])  // C#, COBOL, Java
}");
Console.WriteLine($@"^3..^0: {
    string.Join(", ", languages[^3..^0]) // Python, Lisp, JavaScript
}");
Console.WriteLine($@" 3..^3: {
    string.Join(", ", languages[3..^3]) // C++, TypeScript, Swift
}");
Console.WriteLine($@"  ..^6: {
    string.Join(", ", languages[..^6])  // C#, COBOL, Java
}");
Console.WriteLine($@"   6..: {
    string.Join(", ", languages[6..])  // Python, Lisp, JavaScript
}");
Console.WriteLine($@"    ..: {
    // C#, COBOL, Java, C++, TypeScript, Swift, Python, Lisp, JavaScript
    string.Join(", ", languages[..])  // Python, Lisp, JavaScript
}");
Console.WriteLine($@"    ..: {
    // C#, COBOL, Java, C++, TypeScript, Swift, Python, Lisp, JavaScript
    string.Join(", ", languages[0..^0])  // Python, Lisp, JavaScript
}");

The important thing to note about the range operator is that it identifies the items by specifying the first (inclusive) up to the end (exclusive). Therefore, in the 0..3 example of Listing 3.26, 0 specifies the slice to include everything from the first item up to, but not including, the fourth item (3 is used to identify the fourth item because the forward index starts counting at 0). In Example 2, therefore, specifying ^3..^0 retrieves the last three items. The ^0 does not cause an error by attempting to access the item past the end of the array because the index at the end of the range does not include the identified item.

Specifying either the beginning index or “up to” (the end) index is optional, as shown in Examples 4–6 of Listing 3.26. As such, if indices are missing entirely, it is equivalent to specifying 0..^0.

Finally, note that indices and ranges are first-class types in .NET/C# (as described in the System.Index and System.Range advanced block). Their behavior is not limited to use in an array accessor.

note
The range operator identifies items by specifying the first item – inclusive to the end item – exclusive.

AdVanceD Topic
System.Index and System.Range

Using the index from end operator is a literal way of specifying a System.Index value. Therefore, you can use indexes outside the context of the square brackets. For example, you could declare an index and assign it a literal value: System.Index index = ^42. Regular integers can also be assigned to a System.Index. The System.Index type has two properties, a Value of type int, and an IsFromEnd property of type bool. The latter is obviously used to indicate whether the index counts from the beginning of the array or the end.

In addition, the data type used to specify a range is a System.Range. As such, you can declare and assign a range value: System.Range range = ..^0 or even System.Range range = .., for example. System.Range has two properties, Start and End.

By making these types available, C# enables you to write custom collections that support ranges and the “from the end” indices. (We discuss how to write custom collections in Chapter 17.)

More Array Methods

Arrays include additional methods for manipulating the elements within the array—for example, Sort(), BinarySearch(), Reverse(), and Clear() (see Listing 3.27 with Output 3.2).

Listing 3.27: Additional Array Methods
string[] languages = new string[]{
    "C#""COBOL""Java",
    "C++""TypeScript""Swift",
    "Python""Lisp""JavaScript"};
 
Array.Sort(languages);
 
string searchString = "COBOL";
int index = Array.BinarySearch(
    languages, searchString);
Console.WriteLine(
    "The wave of the future, "
    + $"{ searchString }, is at index { index }.");
 
Console.WriteLine();
Console.WriteLine(
    $""First Element",-20 }\t"Last Element",-20 }");
Console.WriteLine(
    $""-------------",-20 }\t"------------",-20 }");
Console.WriteLine(
        $"{ languages[0],-20 }\t{ languages[^1],-20 }");
Array.Reverse(languages);
Console.WriteLine(
        $"{ languages[0],-20 }\t{ languages[^1],-20 }");
// Note this does not remove all items from the array
// Rather it sets each item to the type's default value
Array.Clear(languages, 0, languages.Length);
Console.WriteLine(
        $"{ languages[0],-20 }\t{ languages[^1],-20 }");
Console.WriteLine(
    $"After clearing, the array size is: { languages.Length }");
Output 3.2
The wave of the future, COBOL, is at index 2.
First Element        Last Element
-------------        ------------
C#                   TypeScript
TypeScript           C#
After clearing, the array size is: 9

Access to these methods is obtained through 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, the incorrect index may be returned. If the search element does not exist, 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 (null, 0, or false). This explains why Console.WriteLine() creates a blank line when writing out the array after Clear() is called.
Array Instance Members

Like strings, arrays have instance members that are accessed not from the data type but directly from the variable. Length is an example of an instance member because access to Length occurs 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 utilize 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 3.28 with Output 3.3).

Listing 3.28: Retrieving a Particular Dimension’s Size
bool[, ,] cells;
cells = new bool[2, 3, 3];
Console.WriteLine(cells.GetLength(0)); // Displays 2
Console.WriteLine(cells.Rank); // Displays 3
Output 3.3
2

Listing 3.28 displays 2 because that 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. cells.Rank, for example, returns 3 (see Listing 3.28).

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 returns a copy of the array; changing any of the members of this new array does 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 3.29 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.

Listing 3.29: Looking for Command-Line Options
public static void Main(string[] args)
{
    // ...
    if(args[0][0] == '-')
    {
        // This parameter is an option
    }
}

This snippet uses the if statement, which is covered in Chapter 4. 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, which retrieves the first character of the string. The code, therefore, is equivalent to that shown in Listing 3.30.

Listing 3.30: Looking for Command-Line Options (Simplified)
public static void Main(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, but it is also possible to retrieve the entire string as an array of characters using the string’s ToCharArray() method. Using this approach, you could reverse the string with the System.Array.Reverse() method, as demonstrated in Listing 3.31 with Output 3.4, which determines whether a string is a palindrome.

Listing 3.31: Reversing a String
string reverse, palindrome;
char[] temp;
 
Console.Write("Enter a palindrome: ");
palindrome = Console.ReadLine();
 
// Remove spaces and convert to lowercase
reverse = palindrome.Replace(" """);
reverse = reverse.ToLower();
 
// Convert to an array
temp = reverse.ToCharArray();
 
// Reverse the array
Array.Reverse(temp);
 
// Convert the array back to a string and
// check if reverse string is the same
if (reverse == new string(temp))
{
    Console.WriteLine(
        $"\"{palindrome}\" is a palindrome.");
}
else
{
    Console.WriteLine(
        $"\"{palindrome}\" is NOT a palindrome.");
}
Output 3.4
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 Array Errors

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

Table 3.3: Common Array Coding Errors

Common Mistake

Error Description

Corrected Code

// 1.

int numbers[];

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

int[] numbers;

// 2.

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 }

// 3.

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 };

// 4.

int[] numbers =

   new int[];

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

int[] numbers =

   new int[3];

// 5.

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 };

// 6.

int[] numbers =

   new int[3];

Console.WriteLine(

   numbers[3]);

Array indices 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]);

// 7.

int[] numbers =

   new int[3];

numbers[^0] = 42;

Same as previous error. The index from end operator uses ^1 to identify the last item in the array. ^0 is one item past the end, which doesn’t exist. (Note that this is a runtime error, not a compile-time error.)

int[] numbers =

   new int[3];

numbers[^1] =

   42;

// 8.

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;

// 9.

int[,] numbers =

   { {42}, {84, 42} };

Multidimensional arrays must be structured consistently.

int[,] numbers =

   { {42, 168},

     {84, 42} };

// 10.

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} };

________________________________________

6. Starting with C# 3.0.
7. Starting with C# 3.0.
{{ snackbarMessage }}
;