Skip to main content
ANVISoftware Solutions
Lesson 30 of 62Intermediate15 min

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.

Creating, reading and walking an array
C#
// 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 and jagged, side by side
C#
// 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 lengthsAll identical, fixed at creationIndependent, each set separately
Memory layoutOne contiguous blockOne outer array plus a separate array per row
Initial contentsDefaults in every cellOuter array of nulls until you fill it
Indexinggrid[2, 5]grid[2][5]
foreach behaviourVisits every cell in one loopVisits each row array, so you nest
Natural fitA genuine grid: a matrix, a monthly tableGroups 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.

C#
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.