Arrays
A fixed-length, zero-indexed run of values of one type: declaring one, reading Length, and staying inside the bounds.
int[] scores = new int[4];An array is a fixed-length run of values, all of the same type, kept in order. You choose the length when you create the array, and that length never changes afterwards. Arrays are the simplest storage the language has, and most other collections are built on one.
Positions start at zero
Each value sits at a numbered position. That number is called an index. The first index is 0, not 1. An array with four slots has indexes 0, 1, 2 and 3. The Length property tells you how many slots there are, so the last valid index is always Length - 1.
A new array is not empty. Every slot already holds a default value: 0 for numbers, false for bool, and null for reference types such as string.
Three ways to write the same thing
All three lines below build the same three-item array of int. They are not different features; the compiler produces the same code for each.
int[] a = new int[] { 1, 2, 3 };
int[] b = { 1, 2, 3 };
int[] c = [1, 2, 3]; // collection expression, C# 12 and later
Where it goes wrong
Reading or writing an index outside the array throws IndexOutOfRangeException while the program runs. The usual cause is a loop that goes up to Length instead of stopping before it, so write the condition as i < array.Length. To reach the last item you can write array[^1], which is shorthand for array[array.Length - 1].
Example
int[] scores = new int[4]; // four slots, and every one starts at 0
scores[0] = 18;
scores[1] = 25;
scores[2] = 31;
scores[3] = 26;
// Length is the number of slots. Valid indexes run from 0 to Length - 1.
Console.WriteLine($"slots: {scores.Length}");
Console.WriteLine($"first: {scores[0]}");
Console.WriteLine($"last: {scores[scores.Length - 1]}");
int total = 0;
for (int i = 0; i < scores.Length; i++)
{
total += scores[i];
}
Console.WriteLine($"total: {total}");
// The length comes from the number of values you list.
string[] names = { "ana", "bo", "cy" };
Console.WriteLine($"names: {string.Join(", ", names)}");
// Output:
// slots: 4
// first: 18
// last: 26
// total: 100
// names: ana, bo, cyWhen to use it
- Holding the twelve monthly totals in a report, where the count is known and never changes.
- Filling a byte buffer of a fixed size before handing it to a file or network call.
- Passing a batch of values to a method that needs them in a known order.
More examples
Walking an array with foreach
When you only need the values and not their positions, foreach removes the whole class of off-by-one mistakes. Reach for a for loop instead when you need the index itself, or when you want to walk the array backwards.
double[] temperatures = { 12.5, 15.0, 9.75, 18.25 };
double highest = temperatures[0]; // start from a real reading, not 0
double total = 0;
// foreach hands you each value in order. You never write an index,
// so you cannot get the index wrong.
foreach (double reading in temperatures)
{
total += reading;
if (reading > highest)
{
highest = reading;
}
}
double average = total / temperatures.Length;
Console.WriteLine($"highest: {highest}");
Console.WriteLine($"average: {average}");
// Output:
// highest: 18.25
// average: 13.875An array cannot grow, so you copy into a bigger one
This is the work that List<T> does for you on every Add. Seeing it once explains why a growable collection exists at all, and why an array is the right choice only when you know the count up front.
string[] names = { "ana", "bo" };
// The length of an array is fixed for its whole life. To "grow" one you
// make a bigger array and copy the old values into it.
string[] bigger = new string[3];
Array.Copy(names, bigger, names.Length); // copies 2 items into positions 0 and 1
bigger[2] = "cy";
Console.WriteLine($"old length: {names.Length}");
Console.WriteLine($"new: {string.Join(", ", bigger)}");
// Array.IndexOf returns the position, or -1 when the value is not there.
Console.WriteLine($"bo is at: {Array.IndexOf(bigger, "bo")}");
Console.WriteLine($"zed is at: {Array.IndexOf(bigger, "zed")}");
// Output:
// old length: 2
// new: ana, bo, cy
// bo is at: 1
// zed is at: -1A rectangular array has a position per dimension
Use a rectangular array for a genuine grid, such as a board or a fixed table of numbers. Note that Length counts every cell, so sizing a loop needs GetLength with the dimension you mean.
// Two rows, three columns, every cell 0.
int[,] grid = new int[2, 3];
// GetLength(0) is the number of rows, GetLength(1) the number of columns.
for (int row = 0; row < grid.GetLength(0); row++)
{
for (int col = 0; col < grid.GetLength(1); col++)
{
grid[row, col] = (row * 3) + col;
}
}
Console.WriteLine($"rows: {grid.GetLength(0)}, columns: {grid.GetLength(1)}");
Console.WriteLine($"total cells: {grid.Length}"); // Length is every cell, not a side
Console.WriteLine($"grid[1, 2]: {grid[1, 2]}");
// Output:
// rows: 2, columns: 3
// total cells: 6
// grid[1, 2]: 5
Discussion