Arrays
By the end of this lesson
Work with fixed-size collections, including multi-dimensional ones.
An array holds a fixed number of values of one type, laid out next to each other in memory. You decide the size when you create it, and that size never changes.
Two consequences follow from "next to each other". Reading any position is fast and costs the same wherever it is, because the machine can calculate exactly where to look. And growing the array is impossible — there may well be something else in memory immediately after it. Adding an item means creating a bigger array and copying everything across.
// Twelve monthly sales figures, all starting at zero.
decimal[] monthlySales = new decimal[12];
monthlySales[0] = 145_000m; // January — positions start at 0
monthlySales[11] = 210_000m; // December — the last position is Length - 1
// Or state the values up front and let the compiler count them.
string[] regions = { "North", "South", "East", "West" };
Console.WriteLine(regions.Length); // 4
Console.WriteLine(regions[2]); // East
for (int i = 0; i < regions.Length; i++)
{
Console.WriteLine($"{i}: {regions[i]}");
}
foreach (string region in regions)
{
Console.WriteLine(region);
}- decimal[] means "an array of decimal". new decimal[12] reserves twelve slots.
- A new array is not empty, it is filled with the default for its type: 0 for numbers, false for bool, null for reference types such as string.
- Positions are numbered from 0, so a twelve-item array has positions 0 to 11. Length is 12.
- { "North", "South", ... } is an array initialiser. The size comes from how many values you wrote.
- Use a for loop when you need the position number, and foreach when you only need the values. foreach is harder to get wrong.
More than one dimension
// Rectangular: 4 regions by 12 months, every row the same width.
decimal[,] salesByRegionAndMonth = new decimal[4, 12];
salesByRegionAndMonth[0, 0] = 145_000m; // North, January
salesByRegionAndMonth[3, 11] = 98_000m; // West, December
Console.WriteLine(salesByRegionAndMonth.GetLength(0)); // 4 — rows
Console.WriteLine(salesByRegionAndMonth.GetLength(1)); // 12 — columns
for (int region = 0; region < salesByRegionAndMonth.GetLength(0); region++)
{
decimal total = 0m;
for (int month = 0; month < salesByRegionAndMonth.GetLength(1); month++)
{
total += salesByRegionAndMonth[region, month];
}
Console.WriteLine($"Region {region} total: {total:N2}");
}
// Jagged: an array of arrays, so rows can differ in length.
string[][] teamsByOffice = new string[3][];
teamsByOffice[0] = new string[] { "Asha", "Ravi" };
teamsByOffice[1] = new string[] { "Meera" };
teamsByOffice[2] = new string[] { "Karan", "Divya", "Sanjay" };
Console.WriteLine(teamsByOffice[2][1]); // Divya
Console.WriteLine(teamsByOffice[2].Length); // 3- decimal[4, 12] with one set of brackets and a comma is a single rectangular block. Every row has exactly twelve columns.
- Length on a rectangular array gives the total number of cells, which is 48 here. Use GetLength(0) and GetLength(1) for the row and column counts.
- string[][] with two sets of brackets is an array whose items are themselves arrays. Each inner array is created separately and can be a different length.
- A jagged array starts as an array of nulls. teamsByOffice[0] is null until you assign an inner array to it, which is a frequent source of confusion.
- Indexing differs: [row, column] for rectangular, [row][column] for jagged.
Which shape to choose:
| Rectangular decimal[,] | Jagged decimal[][] | |
|---|---|---|
| Row lengths | All identical, fixed at creation | Independent, each set separately |
| Memory layout | One contiguous block | One outer array plus a separate array per row |
| Initial contents | Defaults in every cell | Outer array of nulls until you fill it |
| Indexing | grid[2, 5] | grid[2][5] |
| foreach behaviour | Visits every cell in one loop | Visits each row array, so you nest |
| Natural fit | A genuine grid: a matrix, a monthly table | Groups of differing size: teams per office |
That copying problem is not hypothetical. Suppose you are reading orders from a file and you do not know how many there are. With an array you would have to guess a size, then detect when it filled up, allocate a bigger one, copy everything over, and carry on — and get all of that right while also parsing the file.
That work is worth doing exactly once, by someone else, in a reusable type. That type is List, and it is next.
Summary
- An array has a fixed size chosen at creation and cannot grow
- Positions run from 0 to Length - 1, and going past the end throws IndexOutOfRangeException
- A new array is filled with defaults: zero, false, or null for reference types
- Rectangular arrays are one block with uniform rows; jagged arrays are arrays of arrays with independent rows
- Use an array when the size is genuinely fixed, and a growable collection when it is not
Practice
Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.
Try it yourself
Try it yourself
Create a decimal[4, 3] holding sales for four regions across three months. Fill it with any figures you like, then print each region's total and the grand total.
Then print the month with the highest combined sales across all regions.
Show solution
Region totals loop rows on the outside and columns on the inside. Month totals need the opposite nesting, which is the useful thing to notice — the order of the loops decides what you are summing, and swapping them changes the meaning rather than causing an error.
Tracking a best-so-far value and its index is a pattern that recurs constantly. Starting the best at -1 rather than 0 would matter if totals could be negative, which for sales returns they can.
decimal[,] sales =
{
{ 145_000m, 132_000m, 151_000m },
{ 98_000m, 105_000m, 91_000m },
{ 120_000m, 118_000m, 134_000m },
{ 76_000m, 82_000m, 88_000m }
};
decimal grandTotal = 0m;
for (int region = 0; region < sales.GetLength(0); region++)
{
decimal regionTotal = 0m;
for (int month = 0; month < sales.GetLength(1); month++)
{
regionTotal += sales[region, month];
}
grandTotal += regionTotal;
Console.WriteLine($"Region {region}: {regionTotal:N2}");
}
Console.WriteLine($"Grand total: {grandTotal:N2}");
int bestMonth = 0;
decimal bestMonthTotal = decimal.MinValue;
for (int month = 0; month < sales.GetLength(1); month++)
{
decimal monthTotal = 0m;
for (int region = 0; region < sales.GetLength(0); region++)
{
monthTotal += sales[region, month];
}
if (monthTotal > bestMonthTotal)
{
bestMonthTotal = monthTotal;
bestMonth = month;
}
}
Console.WriteLine($"Best month: {bestMonth} at {bestMonthTotal:N2}");Think about it
Think about it
You need to store the daily closing price of one share for the last thirty days, and separately the list of orders a customer has placed, which grows whenever they buy something.
Which of those two is an array, and what specifically makes the other one a poor fit?
Show solution
Thirty days of prices is a natural array. The size is fixed by the requirement, every slot is meaningful, and you will index into it by day offset.
The order history is a poor fit because its size is a property of the customer's behaviour, not of your code. Every new order would mean allocating a larger array and copying the existing entries, and you would have to track how many slots are actually in use separately from the array's Length — because an array of 50 slots holding 7 orders still reports Length 50.
That extra bookkeeping is the real cost. It is not that arrays cannot be made to work; it is that you end up reimplementing List badly alongside the feature you were meant to be writing.
Saved in this browser only.