Your notebook will hold lots of notes, and you don’t know in advance how many. Creating a separate variable for every note won’t work. You need one variable that holds as many values as you like: a list.

Creating and filling a list

C#
// shopping.cs
List<string> shopping = ["bread", "milk"];
shopping.Add("apples");
Console.WriteLine($"There are {shopping.Count} things on the list.");
Console.WriteLine(
    $"The first is {shopping[0]}, the last is {shopping[shopping.Count - 1]}.");
foreach (string item in shopping)
{
    Console.WriteLine($"- {item}");
}
There are 3 things on the list.
The first is bread, the last is apples.
- bread
- milk
- apples

One step at a time:

  • List<string> is the type “list of strings,” a list of texts. The angle brackets say what goes in the list. A list of whole numbers would be List<int>.
  • The square brackets hold the items the list starts with. You write an empty list as [].
  • Add adds an item at the end.
  • Count is the number of items.
  • shopping[0] is the first item. Lists count from 0, not from 1. That’s why the last item has the number Count - 1.
  • The second Console.WriteLine is long, so it runs over two lines. C# doesn’t mind: a statement ends at its semicolon, not at the end of a line.
  • foreach goes through the list from front to back. In each round, the current item is in the variable item.

If you ask for a number that doesn’t exist, such as shopping[3] with three items, the program stops with an ArgumentOutOfRangeException. So check numbers against Count first.

Numbering with for

People count from 1, lists from 0. If you want to show items with numbers, use for and add one:

C#
// numbered.cs
List<string> tasks = ["File taxes", "Fix the bike", "Call Grandma"];
for (int i = 0; i < tasks.Count; i++)
{
    Console.WriteLine($"{i + 1}. {tasks[i]}");
}
1. File taxes
2. Fix the bike
3. Call Grandma

The condition is i < tasks.Count, not <=. With three items, i runs from 0 to 2, exactly the valid numbers.

Removing items

C#
// remove.cs
List<string> tasks = ["File taxes", "Fix the bike", "Call Grandma"];
tasks.Remove("Fix the bike");
tasks.RemoveAt(0);
foreach (string task in tasks)
{
    Console.WriteLine(task);
}
Console.WriteLine(tasks.Contains("Call Grandma"));
Call Grandma
True

Remove looks for an item with exactly this text and removes it. RemoveAt removes the item at a number, here 0, so the first one. The items behind it move up. Contains tells you whether an item with exactly this text is in the list.

Searching

When you search your notes, “exactly this text” is too strict. You want every item that contains a search word anywhere, no matter whether it’s written in upper or lower case. Contains can do that for a single text, too:

C#
// search.cs
List<string> notes = ["Dentist on Tuesday", "Buy bread",
    "Day trip to the lake", "Gift for Tom"];
string search = "day";
int matches = 0;
foreach (string note in notes)
{
    if (note.Contains(search, StringComparison.OrdinalIgnoreCase))
    {
        Console.WriteLine(note);
        matches++;
    }
}
Console.WriteLine($"{matches} matches for \"{search}\".");
Dentist on Tuesday
Day trip to the lake
2 matches for "day".

note.Contains(search, ...) asks whether the search word occurs in this one text. StringComparison.OrdinalIgnoreCase says: “Ignore upper and lower case.” The name is long, but your editor completes it as soon as you type the first few letters.

Besides lists, there are arrays, written like string[] days = ["Mon", "Tue", "Wed"];. They work in a similar way but have a fixed length. For data that should grow, a list is the better choice. You’ll meet arrays again in lesson 10.

Exercise 1

Write names.cs. The program keeps asking for names until it gets an empty line, and collects them in a list. Then it prints how many names there are and lists them with numbers.

Solution 1

C#
// names.cs
List<string> names = [];
while (true)
{
    Console.Write("Name (blank = done): ");
    string name = Console.ReadLine() ?? "";
    if (name == "")
    {
        break;
    }
    names.Add(name);
}

Console.WriteLine($"{names.Count} names:");
for (int i = 0; i < names.Count; i++)
{
    Console.WriteLine($"{i + 1}. {names[i]}");
}
Name (blank = done): Ada
Name (blank = done): Linus
Name (blank = done): Grace
Name (blank = done):
3 names:
1. Ada
2. Linus
3. Grace

The list is created before the loop so that it still exists after the loop, just like the sum in lesson 6.

Exercise 2

Remember the instructions for the literal-minded helper from lesson 1? Now you write them in C#. Take this list:

List<int> numbers = [4, 17, 9, 23, 8];

Write largest.cs, which finds the largest number and prints it, without ready-made tools, using only a loop and an if. Then print the average as well.

Solution 2

C#
// largest.cs
List<int> numbers = [4, 17, 9, 23, 8];
if (numbers.Count == 0)
{
    Console.WriteLine("The list is empty.");
}
else
{
    int largest = numbers[0];
    int sum = 0;
    foreach (int number in numbers)
    {
        if (number > largest)
        {
            largest = number;
        }
        sum += number;
    }
    double average = (double)sum / numbers.Count;
    Console.WriteLine($"The largest number is {largest}.");
    Console.WriteLine($"The average is {average}.");
}
The largest number is 23.
The average is 12.2.

Step by step, these are the instructions from lesson 1, including the empty list. For the average, you divide the sum (61) by the count (5). If both were int, the integer division from lesson 4 would give you just 12. (double)sum first turns the sum into a number with decimal places. As in lesson 4, a computer set to another language, German for example, may show 12,2 with a comma instead of 12.2.

C# also comes with ready-made tools for jobs like this, such as numbers.Max(). You’ll get to know them in the follow-up course. But once you’ve written it yourself, you know what goes on inside.

Exercise 3

Write search-list.cs. It starts with a list of five notes of your choice, asks for a search word and shows every note that contains it, no matter whether it’s written in upper or lower case. If nothing matches, it prints “Nothing found.”

Solution 3

C#
// search-list.cs
List<string> notes = ["Dentist on Tuesday", "Buy bread", "Day trip to the lake",
    "Gift for Tom", "Change the tires"];
Console.Write("What are you looking for? ");
string search = Console.ReadLine() ?? "";
bool found = false;
foreach (string note in notes)
{
    if (note.Contains(search, StringComparison.OrdinalIgnoreCase))
    {
        Console.WriteLine($"- {note}");
        found = true;
    }
}
if (!found)
{
    Console.WriteLine("Nothing found.");
}
What are you looking for? buy
- Buy bread

Instead of a counter, a bool variable remembers here whether there was at least one match. Both are correct. What happens if you type nothing at all and press Enter right away? An empty text is contained in every text, so all the notes appear. For a search, that’s actually handy. In the notebook, you’ll catch it anyway.

What you can do now

  • You create a List<string> or a List<int>, an empty one too, with [].
  • You add with Add, count with Count, read with [number] and remove with Remove and RemoveAt.
  • You know that lists count from 0, and you still show numbers starting at 1.
  • You go through a list with foreach and for.
  • You search with Contains and StringComparison.OrdinalIgnoreCase.
  • You’ve written your first algorithm yourself.

Read more