C# from the ground up

Arrays and lists

A fixed row of values and a list that grows, indexes that start at nought, and the two everyday ways to walk them.

Part 7 of 13

Intermediate16 min readBack to Courses

One name for many values

An array holds several values of one type, reached through one name and a position. new[] { "Mon", "Tue", "Wed" } makes three strings, and days[0] reads the first of them. The square brackets are doing the work here: they mean "at position", both when the array is created and when a value is read out of it.

Positions start at nought, not one. The first item is days[0] and the last is days[days.Length - 1], which is why a loop written as for (var i = 0; i < days.Length; i++) is the usual shape: it stops before the length, so it never asks for an item that is not there.

An array is fixed, a list grows

An array has the length it was created with. Nothing can be added or removed, which makes it small, fast and predictable — the right choice when the values are known up front.

A List<T> is the flexible one: new List<string>() starts empty, Add puts a value on the end, Remove takes the first match out, and Count says how many are in it. The angle brackets hold the type of the values inside, so List<decimal> is a list of prices and List<string> is a list of names. The list is the type to reach for when the number of values is not known while writing the program.

A list can also be built in one line: new List<string> { "Mon", "Tue" } calls Add for each value, and AddRange takes a whole collection at once. Neither changes the rule that the list owns its own storage: the values are copied in, so changing the array afterwards leaves the list as it was.

Reading them out

Both are walked with foreach, and both answer to [i] and Count or Length. A list can also be handed to a method that wants a list, and an array to one that wants an array, so it is worth deciding early which of the two the program needs.

A list built from an existing array is a common move: new List<string>(days) copies the items in, and from then on the copy can be changed while the array stays as it was. That sentence is the whole difference in one line.

A fixed array and a growing list

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // An array has the length it was created with.
        var days = new[] { "Mon", "Tue", "Wed" };
        Console.WriteLine(days.Length);      // 3
        Console.WriteLine(days[0]);          // Mon

        // A list grows and shrinks: Add, Remove, Count.
        var tasks = new List<string>();
        tasks.Add("write");
        tasks.Add("test");
        Console.WriteLine(tasks.Count);      // 2
        tasks.Remove("write");
        Console.WriteLine(tasks[0]);         // test

        // The same foreach walks either one.
        foreach (var day in days)
        {
            Console.WriteLine(day);
        }

        // Numbers in a list, added up by hand.
        var scores = new List<int> { 7, 8, 9 };
        var total = 0;
        foreach (var n in scores) { total = total + n; }
        Console.WriteLine(total);            // 24
    }
}

Weekdays, and a list that grows

Run it and read the six lines. Then add two more names to the list and print how many are in it before and after, and add the number 23 to the numbers list.

Output
Click "Run" to execute your code

Read days.Length or tasks.Count rather than a number you remember. A loop that trusts its own count is the one that breaks when the data changes.

An array is one block; a list is a wrapper

An array is a single block of memory sized when it is created. That is why its length never changes and why reading days[2] is a direct step to the third slot. The index is checked at run time, so an index outside the block throws IndexOutOfRangeException rather than reading whatever happens to be next.

A List<T> keeps an array inside it and grows that array when it fills. Add usually appends in place; when there is no room, the list allocates a bigger array and copies the items across. That is why adding is cheap most of the time, and why the list, not the array, is the type for a count you do not know yet.

Two names, one collection

Arrays and lists are reference types. Assigning one to another name copies the reference, not the values, so both names point at the same collection. var copy = days; then copy[0] = "Sun"; changes what days[0] reads too.

A real copy needs a new collection: new List<string>(days) or (string[])days.Clone(). Deciding which you meant is part of the design, and the language will not decide it for you.

A method that takes a List<string> receives the same reference, so adding inside the method changes the list in the caller. A method that takes an array can change the slots but cannot resize it. Both facts follow from the same rule.

Three collection mistakes: using Count on an array or Length on a list, which does not compile; reading items[items.Count], one past the end, instead of items[items.Count - 1]; and removing items inside a foreach, which throws because the collection changed under the loop.

Try these next

  • Build a list, insert an item at position one, remove the item at position zero, and print the result after each step.
  • Copy a list with new List<string>(original), change the copy, and print both to show they are separate.
  • Write a loop that prints every item with its index, then rewrite it with foreach and say what was lost.

A list edited in place

Run it and read the seven lines. Then insert a second task at position zero, remove the last task, and print the count after each change.

Output
Click "Run" to execute your code

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

Where is the first item of an array?