Arrays in C#
Single and multi-dimensional arrays, array methods, LINQ, and Span<T> in C#.
Declaring and Initializing Arrays
An array is a fixed-size, ordered collection of elements of the same type. Because the size is fixed at creation, arrays have predictable memory layout and fast indexed access. They are the right choice when you know the number of elements upfront and do not need to add or remove items later.
// Declaration and initialization — size is set at creation and cannot change
int[] numbers = new int[5]; // [0, 0, 0, 0, 0] — zero-initialized
string[] names = new string[3]; // [null, null, null]
// Array initializer — size inferred from the number of elements
int[] primes = { 2, 3, 5, 7, 11 };
string[] colors = new string[] { "red", "green", "blue" };
var scores = new[] { 95, 87, 76, 92 }; // type inferred as int[]
// Access by index (0-based)
Console.WriteLine(primes[0]); // 2
Console.WriteLine(primes[^1]); // 11 (index from end, C# 8+)
primes[2] = 99; // mutation — arrays are mutable by default
// Length
Console.WriteLine(primes.Length); // 5
Iterating Arrays
C# gives you several ways to iterate arrays. foreach is cleanest when you only need the values. for is necessary when you need the index or want to modify elements in place.
int[] numbers = { 10, 20, 30, 40, 50 };
// foreach — clean, no index management
foreach (int n in numbers)
Console.Write(n + " "); // 10 20 30 40 50
// for — use when you need the index or want to write to elements
for (int i = 0; i < numbers.Length; i++)
Console.WriteLine($"[{i}] = {numbers[i]}");
// Reverse iteration
for (int i = numbers.Length - 1; i >= 0; i--)
Console.Write(numbers[i] + " "); // 50 40 30 20 10
// Index and Range (C# 8+) — slice without copying
int[] last3 = numbers[2..]; // [30, 40, 50] — from index 2 to end
int[] middle = numbers[1..4]; // [20, 30, 40] — index 1 up to (not including) 4
int[] copy = numbers[..]; // full copy
Multi-Dimensional Arrays
C# supports two kinds of multi-dimensional arrays: rectangular (all rows same length) and jagged (each row is an independent array of any length). Rectangular arrays are better for grids and matrices; jagged arrays are better for triangular data or when rows vary in size.
// Rectangular 2D array — grid[row, col]
int[,] grid = new int[3, 4]; // 3 rows, 4 columns
grid[0, 0] = 1;
grid[2, 3] = 99;
// Initialize with values
int[,] matrix = {
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 }
};
Console.WriteLine(matrix[1, 2]); // 6 (row 1, col 2)
Console.WriteLine(matrix.GetLength(0)); // 3 (rows)
Console.WriteLine(matrix.GetLength(1)); // 3 (cols)
// Iterate a 2D array
for (int row = 0; row < matrix.GetLength(0); row++)
{
for (int col = 0; col < matrix.GetLength(1); col++)
Console.Write($"{matrix[row, col]} ");
Console.WriteLine();
}
// Jagged array — array of arrays, each row independent
int[][] jagged = new int[3][];
jagged[0] = new int[] { 1, 2 };
jagged[1] = new int[] { 3, 4, 5 };
jagged[2] = new int[] { 6 };
// Each row can have a different length
foreach (int[] row in jagged)
{
foreach (int val in row)
Console.Write(val + " ");
Console.WriteLine();
}
Array Methods
The Array class provides static utility methods for common operations like sorting, searching, and copying. These are well-optimized and handle edge cases you would otherwise need to write yourself.
int[] numbers = { 5, 3, 8, 1, 9, 2, 7, 4, 6 };
// Sort — in-place, modifies the original array
Array.Sort(numbers);
Console.WriteLine(string.Join(", ", numbers)); // 1, 2, 3, 4, 5, 6, 7, 8, 9
// Reverse — in-place
Array.Reverse(numbers);
Console.WriteLine(string.Join(", ", numbers)); // 9, 8, 7, 6, 5, 4, 3, 2, 1
// Binary search — requires a sorted array, returns index or negative value
Array.Sort(numbers);
int idx = Array.BinarySearch(numbers, 5); // index of 5
Console.WriteLine(idx); // 4
// IndexOf — finds the first occurrence
int pos = Array.IndexOf(numbers, 7); // index of 7 in unsorted search
// Copy — copy elements between arrays
int[] source = { 1, 2, 3, 4, 5 };
int[] dest = new int[5];
Array.Copy(source, dest, source.Length); // copy all elements
// Fill — set all elements to a value
int[] filled = new int[5];
Array.Fill(filled, 42); // [42, 42, 42, 42, 42]
// Clear — reset elements to default (0 for int, null for reference types)
Array.Clear(numbers, 0, 3); // clear first 3 elements
LINQ with Arrays
LINQ (Language Integrated Query) lets you query and transform arrays using a composable, declarative style. Rather than writing loops with accumulator variables, you express what you want and let LINQ handle the iteration. LINQ works on any IEnumerable<T>, which includes all arrays.
int[] numbers = { 5, 3, 8, 1, 9, 2, 7, 4, 6 };
// Filter
int[] evens = numbers.Where(n => n % 2 == 0).ToArray(); // [8, 2, 4, 6]
// Transform
int[] doubled = numbers.Select(n => n * 2).ToArray(); // [10, 6, 16, ...]
// Aggregate
int sum = numbers.Sum(); // 45
double avg = numbers.Average(); // 5.0
int max = numbers.Max(); // 9
int min = numbers.Min(); // 1
// Order
int[] sorted = numbers.OrderBy(n => n).ToArray();
int[] desc = numbers.OrderByDescending(n => n).ToArray();
// Combine operations — filter, then sort, then take top 3
int[] top3 = numbers
.Where(n => n > 3)
.OrderByDescending(n => n)
.Take(3)
.ToArray(); // [9, 8, 7]
// Useful predicates
bool anyOver8 = numbers.Any(n => n > 8); // true
bool allPositive = numbers.All(n => n > 0); // true
int firstEven = numbers.First(n => n % 2 == 0); // 8
int? firstBig = numbers.FirstOrDefault(n => n > 100); // null
// GroupBy — group numbers into even and odd
var groups = numbers.GroupBy(n => n % 2 == 0 ? "even" : "odd");
foreach (var group in groups)
Console.WriteLine($"{group.Key}: {string.Join(", ", group)}");
Span<T> for Array Slicing
Span<T> is a view into a contiguous block of memory. When you slice an array with Span<T>, you get a window into the original array’s storage — no copying. This is a significant performance advantage when passing array segments to methods, especially in parsing or data processing code.
int[] data = { 1, 2, 3, 4, 5, 6, 7, 8 };
// Slice without copying — span is a view into data's memory
Span<int> slice = data.AsSpan(2, 4); // elements at index 2, 3, 4, 5
Console.WriteLine(slice[0]); // 3
// Modifications through span affect the original array
slice[0] = 99;
Console.WriteLine(data[2]); // 99
// Pass a slice to a method — no array allocation
void ProcessSegment(ReadOnlySpan<int> segment)
{
foreach (int n in segment)
Console.Write(n + " ");
}
ProcessSegment(data.AsSpan(0, 4)); // passes first 4 elements, no copy
// stackalloc — allocate a small array on the stack (no GC pressure at all)
Span<int> buffer = stackalloc int[8];
for (int i = 0; i < buffer.Length; i++)
buffer[i] = i * i; // [0, 1, 4, 9, 16, 25, 36, 49]
Common Array Patterns
These patterns come up frequently enough that it is worth knowing the idiomatic C# approach for each.
// Initialize with computed values — alternative to a loop
int[] squares = Enumerable.Range(1, 10).Select(n => n * n).ToArray();
// [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
// Flatten a jagged array
int[][] jagged = { new[] { 1, 2 }, new[] { 3, 4, 5 }, new[] { 6 } };
int[] flat = jagged.SelectMany(row => row).ToArray(); // [1, 2, 3, 4, 5, 6]
// Convert between arrays and lists
List<int> list = new List<int> { 1, 2, 3 };
int[] array = list.ToArray();
List<int> backToList = array.ToList();
// Check if two arrays have the same contents
int[] a = { 1, 2, 3 };
int[] b = { 1, 2, 3 };
bool equal = a.SequenceEqual(b); // true — element-by-element comparison