Arrays — Fixed-Size Collections and Iteration

Arrays in C# are fixed-size, zero-indexed, single-type collections allocated on the heap. They are the most fundamental collection and underpin all other .NET collections. While day-to-day ASP.NET Core code uses List<T> and IEnumerable<T> more often, arrays appear constantly in method signatures, deserialized JSON payloads, params arguments, and framework APIs. C# 8 introduced the index-from-end (^) and range (..) operators that make array slicing expressive and readable.

Declaration and Initialisation

// ── Declaration with new ──────────────────────────────────────────────────────
int[]    scores = new int[5];          // 5 elements, all default to 0
string[] names  = new string[3];       // 3 elements, all null
bool[]   flags  = new bool[4];         // 4 elements, all false

// ── Initialiser syntax (size inferred from element count) ─────────────────────
int[]    primes = { 2, 3, 5, 7, 11 };
string[] days   = new string[] { "Mon", "Tue", "Wed", "Thu", "Fri" };
var      nums   = new[] { 1.0, 2.0, 3.0 };   // inferred as double[]

// ── Element access (zero-based index) ─────────────────────────────────────────
int first  = primes[0];      // 2  — first element
int last   = primes[4];      // 11 — last element (Length - 1)
int length = primes.Length;  // 5  — total count

// ── C# 8+ index-from-end (^) ──────────────────────────────────────────────────
int lastEl     = primes[^1];  // 11 — same as primes[primes.Length - 1]
int secondLast = primes[^2];  // 7

// ── C# 8+ range (..) — start inclusive, end exclusive ────────────────────────
int[] middle = primes[1..4];  // { 3, 5, 7 }  — indices 1, 2, 3
int[] first3 = primes[..3];   // { 2, 3, 5 }  — first 3 elements
int[] last2  = primes[^2..];  // { 7, 11 }    — last 2 elements
Note: Arrays are reference types — passing an array to a method passes the reference, so the method can modify the original array’s elements. If you need an isolated copy, use Array.Copy() or LINQ’s .ToArray() on a new sequence. In ASP.NET Core, model binding deserializes arrays from JSON into new array objects, so accidental mutation is rarely a problem at the API layer — but it matters in service and domain layer code where arrays are passed between methods.
Tip: Prefer foreach over for when you do not need the index — it is more readable and impossible to get an off-by-one error. Use for when you need the index to access neighbouring elements, write back to the array, or iterate in reverse. In practice, most collection processing in ASP.NET Core uses LINQ (.Where(), .Select() etc. from Chapter 9) which is even more expressive than either loop type.
Warning: Accessing an array with an out-of-bounds index throws IndexOutOfRangeException at runtime — there is no automatic null-return or default value. The most common off-by-one error is using <= instead of < in the loop condition: for (int i = 0; i <= scores.Length; i++) will throw on the last iteration when i == scores.Length. Always use < in array loops, or use foreach to avoid the issue entirely.

Iterating Arrays

int[] scores = { 95, 87, 72, 91, 68 };

// foreach — preferred when index is not needed
foreach (int score in scores)
    Console.Write($"{score} ");   // 95 87 72 91 68

// for — use when the index is needed
for (int i = 0; i < scores.Length; i++)
    Console.WriteLine($"[{i}] = {scores[i]}");

// Reverse
for (int i = scores.Length - 1; i >= 0; i--)
    Console.Write(scores[i] + " ");   // 68 91 72 87 95

// Sum and average without LINQ
int sum = 0;
foreach (int score in scores) sum += score;
double avg = (double)sum / scores.Length;
Console.WriteLine($"Average: {avg:F1}");   // Average: 82.6

System.Array Static Methods

int[] nums = { 5, 2, 8, 1, 9, 3 };

Array.Sort(nums);                           // { 1, 2, 3, 5, 8, 9 } in-place
Array.Reverse(nums);                        // { 9, 8, 5, 3, 2, 1 } in-place
int idx    = Array.IndexOf(nums, 5);        // index of value 5, or -1
int found  = Array.Find(nums, n => n > 7); // first element where predicate is true
int[] all  = Array.FindAll(nums, n => n > 3);  // all elements matching predicate
Array.Fill(nums, 0);                        // set all elements to 0

// Copy
int[] copy = new int[nums.Length];
Array.Copy(nums, copy, nums.Length);        // copy elements to an existing array

Common Mistakes

Mistake 1 — Off-by-one with <= in the loop bound

❌ Wrong — throws IndexOutOfRangeException on the last iteration:

for (int i = 0; i <= scores.Length; i++)   // i reaches scores.Length — invalid!

✅ Correct:

for (int i = 0; i < scores.Length; i++)    // i goes 0..Length-1 only

Mistake 2 — Attempting to resize an array

Arrays have a fixed size set at creation. Use List<T> (Chapter 7) when you need a dynamically-sized collection, and call .ToArray() only if an array is ultimately required by the API you are calling.

🧠 Test Yourself

Given int[] arr = { 10, 20, 30, 40, 50 }, what does arr[^2] return and what does arr[1..4] return?