C# Arrays Explained: Types, Features, and Operations


Arrays are the workhorses of programming and, in C#, they play a pivotal role in managing collections of data efficiently. Understanding how to declare, manipulate, and apply them is key to unlocking the full potential of the language.


What is an Array in C#?

In C#, an array is a collection of elements of the same type, stored in contiguous memory locations. Arrays allow you to store and manage multiple values under a single variable name, making data handling more efficient.

Arrays have the following key Characteristics:

  • Fixed Size: Once an array is created, its size cannot be changed.
  • Zero-Based Indexing: Elements are accessed using zero-based indices.
  • Type Safety: All elements must be of the same type.

Declaring Arrays

Single-Dimensional Arrays

The simplest form of an array can be declared and initialised as follows:

// Declaration
int[] numbers;

// Initialisation
numbers = new int[5]; // Array of size 5

// Declaration and Initialisation
int[] primes = new int[] { 2, 3, 5, 7, 11 };

// Implicit Typing
var fruits = new[] { "Apple", "Banana", "Cherry" }; 
// Type inferred as string[]

// shorthand
int[] evenNumbers = { 2, 4, 6, 8, 10 }; // no need for new int[]

Multi-Dimensional Arrays

Rectangular array refers to a multidimensional array where all rows have the same number of columns. This structure is often referred to as a 2D array and can be declared as below:

int[,] matrix = new int[3, 3]; // 3x3 matrix

// Initialisation
int[,] grid = {
    { 1, 2, 3 },
    { 4, 5, 6 },
    { 7, 8, 9 }
};

We can also declare arrays with more than 2 dimensions:

int[,,] threeDArray = 
{
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 }
},
{
{ 13, 14, 15, 16 },
{ 17, 18, 19, 20 },
{ 21, 22, 23, 24 }
}
};

Jagged Arrays are arrays of arrays where rows can have different sizes:





int[][] jaggedArray = new int[3][];
jaggedArray[0] = new int[] { 1, 2 };
jaggedArray[1] = new int[] { 3, 4, 5 };
jaggedArray[2] = new int[] { 6 };

A few key points:

Featureint[,] (Rectangular Array)int[][] (Jagged Array)
StructureRectangular (fixed rows & columns)Jagged (rows can have varying lengths)
Memory AllocationSingle contiguous block of memorySeparate memory for each row
Access Syntaxarray[row, column]array[row][column]
InitialisationFixed size for all dimensionsFlexible size for each row
Performance Slightly faster due to contiguous memoryMay have slight overhead due to multiple references
FlexibilityLess flexible; fixed dimensionsHighly flexible; varying row lengths

Accessing and Iterating Over Arrays

We access the elements of an array using their index:

int[] arr = new int[] { 2, 3, 5, 7, 11 };
Console.WriteLine(arr[0]); // Output: 2
arr[2] = 13; // Update the value at index 2

int[,] multiArray = new int[2, 3]; // 2 rows, 3 columns

// Assign values
multiArray[0, 0] = 1;
multiArray[0, 1] = 2;
multiArray[1, 2] = 3;

// Access values
Console.WriteLine(multiArray[0, 1]); // Output: 2

int[][] jaggedArray = new int[2][]; // Array with 2 rows

// Initialize rows with different lengths
jaggedArray[0] = new int[3]; // First row has 3 elements
jaggedArray[1] = new int[2]; // Second row has 2 elements

// Assign values
jaggedArray[0][0] = 1;
jaggedArray[1][1] = 5;

// Access values
Console.WriteLine(jaggedArray[0][0]); // Output: 1
Console.WriteLine(jaggedArray[1][1]); // Output: 5

We can iterate over the elements of an array using a for loop:

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

We can also use a foreach loop:

foreach (var element in arr) {
    Console.WriteLine(element);
}

We can iterate over a multidimensional array with a few nested loops:

int[,,] threeDArray = new int[2, 2, 3]
{
{ { 1, 2, 3 }, { 4, 5, 6 } },
{ { 7, 8, 9 }, { 10, 11, 12 } }
};

for (int layer = 0; layer < threeDArray.GetLength(0); layer++)
{
for (int row = 0; row < threeDArray.GetLength(1); row++)
{
for (int col = 0; col < threeDArray.GetLength(2); col++)
{
Console.WriteLine($"Element at [{layer}, {row}, {col}] = {threeDArray[layer, row, col]}");
}
}
}

Getting Information About an Array

In C#, arrays have several built-in properties and methods that allow you to query and interact with arrays effectively.

Length returns the total number of elements in the array, regardless of its dimensions.

int[] numbers = { 1, 2, 3, 4, 5 };
Console.WriteLine($"Length: {numbers.Length}"); // Output: 5

int[,] matrix = { { 1, 2, 3 }, { 4, 5, 6 } };
Console.WriteLine($"Total Elements: {matrix.Length}"); // Output: 6

Rank returns the number of dimensions (or ranks) in the array.

int[,] matrix = { { 1, 2 }, { 3, 4 } };

Console.WriteLine($"Rank: {matrix.Rank}"); // Output: 2

int[] singleDimensional = { 1, 2, 3 };

Console.WriteLine($"Rank: {singleDimensional.Rank}"); // Output: 1

GetLength returns the size of a specific dimension in the array.

int[,] matrix = { { 1, 2, 3 }, { 4, 5, 6 } };
Console.WriteLine($"Number of Rows: {matrix.GetLength(0)}"); // Output: 2
Console.WriteLine($"Number of Columns: {matrix.GetLength(1)}"); // Output: 3

GetLowerBound returns the starting index of a specific dimension. For standard arrays in C#, this is always 0. However, it has its uses in scenarios with custom collections or systems where non-zero-based arrays are present (e.g., interop with other languages).

int[,] matrix = { { 1, 2 }, { 3, 4 } };

Console.WriteLine($"Lower Bound of Dimension 0: {matrix.GetLowerBound(0)}"); // Output: 0

Console.WriteLine($"Lower Bound of Dimension 1: {matrix.GetLowerBound(1)}"); // Output: 0

Get UpperBound returns the highest index (upper bound) of a specific dimension, which is useful for iterating through specific dimensions of multidimensional arrays.

int[,] matrix = { { 1, 2 }, { 3, 4 } };

Console.WriteLine($"Upper Bound of Rows: {matrix.GetUpperBound(0)}"); // Output: 1

Console.WriteLine($"Upper Bound of Columns: {matrix.GetUpperBound(1)}"); // Output: 1

Common Array Operations

C# is a fully object oriented language and, despite being fundamental data structures, arrays are reference types derived from System.Array and inherit from System.Object. We’ll explore this in future posts. For now, let’s address some of the most common operations with arrays using the functionalities provided by the System.Array class.

Array.Sort sorts the elements of an array in ascending order.

int[] numbers = { 5, 2, 8, 1, 3 };
Array.Sort(numbers);
Console.WriteLine("Sorted Array:");
Console.WriteLine(string.Join(", ", numbers)); // Output: 1, 2, 3, 5, 8

Array.Reverse reverses the order of elements in an array.

int[] numbers = { 1, 2, 3, 4, 5 };
Array.Reverse(numbers);
Console.WriteLine("Reversed Array:");
Console.WriteLine(string.Join(", ", numbers)); // Output: 5, 4, 3, 2, 1

Array.IndexOf Finds the index of the first occurrence of a specific value in the array.

string[] fruits = { "apple", "banana", "cherry", "date" };
int index = Array.IndexOf(fruits, "cherry");
Console.WriteLine($"Index of 'cherry': {index}"); // Output: 2

Array.LastIndexOf finds the index of the last occurrence of a specific value in the array.

int[] numbers = { 1, 2, 3, 2, 5 };
int index = Array.LastIndexOf(numbers, 2);
Console.WriteLine($"Last Index of 2: {index}"); // Output: 3

Array.Exists determines if an array contains elements that match a condition.

int[] numbers = { 10, 20, 30, 40 };
bool exists = Array.Exists(numbers, x => x > 25);
Console.WriteLine($"Exists element > 25: {exists}"); // Output: True

Array.Copy copies elements from one array to another.

int[] source = { 1, 2, 3, 4, 5 };
int[] destination = new int[3];
Array.Copy(source, 1, destination, 0, 3);
Console.WriteLine("Copied Array:");
Console.WriteLine(string.Join(", ", destination)); // Output: 2, 3, 4

Array.Clear clears the elements of an array, setting them to their default value.

int[] numbers = { 1, 2, 3, 4, 5 };
Array.Clear(numbers, 1, 3);
Console.WriteLine("Cleared Array:");
Console.WriteLine(string.Join(", ", numbers)); // Output: 1, 0, 0, 0, 5

Array.Resize changes the size of an array.

int[] numbers = { 1, 2, 3 };
Array.Resize(ref numbers, 5);
numbers[3] = 4;
numbers[4] = 5;
Console.WriteLine("Resized Array:");
Console.WriteLine(string.Join(", ", numbers)); // Output: 1, 2, 3, 4, 5

Array.Find returns the first element that matches a condition.

int[] numbers = { 5, 15, 25, 35 };
int result = Array.Find(numbers, x => x > 20);
Console.WriteLine($"First element > 20: {result}"); // Output: 25

Array.FindAll returns all elements that match a condition.

int[] numbers = { 1, 2, 3, 4, 5, 6 };
int[] evens = Array.FindAll(numbers, x => x % 2 == 0);
Console.WriteLine("Even Numbers:");
Console.WriteLine(string.Join(", ", evens)); // Output: 2, 4, 6

Array.ForEach performs an action on each element of the array.

int[] numbers = { 1, 2, 3 };
Array.ForEach(numbers, x => Console.WriteLine(x * x)); // Output: 1, 4, 9

Best Practices for Working with Arrays in C#

A few good practices when for the use of arrays:

  • Use Arrays when the size of the collection is fixed or predictable and you need a fast, index-based structure with minimal memory overhead.
  • Use Descriptive Variable Names. Name arrays based on their purpose or contents for better code readability.
  • Initialise arrays properly to prevent runtime errors.
  • Always validate array indexes to prevent runtime errors.
  • Leverage the Array class methods to simplify common tasks like sorting and searching.
  • Arrays are fixed in size, so resizing involves creating a new array and copying elements, which is inefficient. Use Array.Resize only when absolutely necessary.
  • For efficiency, choose the type that fits your data structure and access patterns.
  • For simple iterations, prefer foreach as it’s cleaner and avoids index-related bugs.
  • Leverage array properties like Length, Rank, GetLowerBound, and GetUpperBound for safer and dynamic programming.
  • Keep array operations simple and avoid deeply nested loops when possible. If logic becomes too complex, encapsulate it in methods.
  • For large arrays, explicitly nullify or dispose of references to free memory earlier if they are no longer needed (helpful in long-running applications).
int[] largeArray = new int[1_000_000];
// Use the array
largeArray = null; // Mark for garbage collection

Wrapping It Up

Arrays are foundational to programming in C#, offering a simple yet powerful way to handle collections of data. From basic declarations to advanced manipulations, mastering these concepts will make you a more effective C# developer.

Whether you’re building applications that require performance optimization or just managing data efficiently, arrays are a tool you’ll return to time and again.

Leave a comment