TOC

The community is working on translating this tutorial into Turkish, but it seems that no one has started the translation process for this article yet. If you can help us, then please click "More info".

Collections:

Lists

C# has a range of classes for dealing with lists. They implement the IList interface and the most popular implementation is the generic list, often referred to as List<T>. The T specifies the type of objects contained in the list, which has the added benefit that the compiler will check and make sure that you only add objects of the correct type to the list - in other words, the List<T> is type-safe.

List is much like the ArrayList class, which was the go-to List choice before C# supported generic lists. Therefore you will also see that the List can do a lot of the same stuff as an Array (which also implements the IList interface by the way), but in a lot of situations, List is simpler and easier to work with. For instance, you don't have to create a List with a specific size - instead, you can just create it and .NET will automatically expand it to fit the amount of items as you add them.

As mentioned, the T stands for type and is used to specify the type of objects you want the list to contain. In our first example, I will show you how to create a list which should contain strings:

List<string> listOfStrings = new List<string>();

This creates an empty list, but adding stuff to it afterwards is very easy with the Add method:

listOfStrings.Add("a string");

However, if you try to add something that is not a string, the compiler will immediately complain about it:

listOfStrings.Add(2);
Error   CS1503  Argument 1: cannot convert from 'int' to 'string'

Initializing a list with items

In the above example, we just created a list and then we added an item to it. However, C# actually allows you to create a list AND add items to it within the same statement, using a technique called collection initializers. Let's see how it's done:

List<string> listOfNames = new List<string>()
{
    "John Doe",
    "Jane Doe",
    "Joe Doe"
};

The syntax is quite simple: Before the usual ending semicolon, we have a set of curly brackets, which in turn holds a list of the values we want to be present in the list from the start. Since this is a list of strings, the initial objects we provide should of course be of the string type. However, the exact same can be accomplished for list of other types, even if we're using our own classes, as I will demonstrate in the next example.

Working with the items

There are several ways to work with the items of a generic list and to show you some of them, I have created a larger example:

using System;
using System.Collections.Generic;

namespace Lists
{
    class Program
    {
static void Main(string[] args)
{
    List<User> listOfUsers = new List<User>()
    {
new User() { Name = "John Doe", Age = 42 },
new User() { Name = "Jane Doe", Age = 34 },
new User() { Name = "Joe Doe", Age = 8 },
    };

    for(int i = 0; i < listOfUsers.Count; i++)
    {
Console.WriteLine(listOfUsers[i].Name + " is " + listOfUsers[i].Age + " years old");
    }
    Console.ReadKey();
}
    }

    class User
    {
public string Name { get; set; }

public int Age { get; set; }
    }
}

Let's start from the bottom, where we define a simple class for holding information about a User - just a name and an age. Back to the top part of the example, where I have changed our list to use this User class instead of simple strings. I use a collection initializer to populate the list with users - notice how the syntax is the same as before, just a bit more complex because we're dealing with a more complex object than a string.

Once we have the list ready, I use a for loop to run through it - to know how many iterations we're going to do, I use the Count property of the List. On each iteration, I access the user in question through the indexer of the list, using the square bracket syntax (e.g. listOfUsers[i]). Once I have the user, I output name and age.

Adding, Inserting & Removing items

We already tried adding a single item to a list, but there are more options for doing this. First of all, you can Insert an item instead of adding it - the difference is that while the Add method always adds to the end of the list, the Insert method allows you to insert an item at a specific position. Here's an example:

List<string> listOfNames = new List<string>()
{
    "Joe Doe"
};
// Insert at the top (index 0)
listOfNames.Insert(0, "John Doe");
// Insert in the middle (index 1)
listOfNames.Insert(1, "Jane Doe");

We start the list of with just one item, but then we insert two more items, first at the top of the list and then in the middle. The first parameter of the Insert method is the index where we want to insert the item. Be careful though - an exception will be thrown if you try to insert an item at index 3, if the list has less items!

Adding multiple items

Just like we have the Add and Insert methods for adding a single item, there are also corresponding methods for adding and inserting multiple items. They are called AddRange() and InsertRange() and accepts any type of collection which implements the IEnumerable interface as a parameter - this could be e.g. an array of items or another list, which items you want to add or insert into the current list.

As an example on the Range methods, let's do something fun - we combine the AddRange method with a collection initializer to add several new names to an existing list in a single statement:

listOfNames.AddRange(new string[]
    {
"Jenna Doe",
"Another Doe"
    });

We simply create an array of strings on-the-fly and immediately append its items to our list of names from the previous example.

Removing items

There are currently three methods at your disposal when you want to remove one or several items from a list: Remove(), RemoveAt() and RemoveAll().

The Remove() method takes just one parameter: The item you want to remove. This is great for e.g. a list of strings or integers, because you can simply just write the item you want to remove. On the other hand, if you have a list of complex objects, you would have to find that object first, to have a reference you could pass to the Remove() method.¨Let's deal with that later - here's a very basic example on how you can remove a single item with the Remove() method:

List<string> listOfNames = new List<string>()
{
    "John Doe",
    "Jane Doe",
    "Joe Doe",
    "Another Doe"
};

listOfNames.Remove("Joe Doe");

The Remove() method simply iterates through the list until it finds the first instance of the object you specified for removal, and them removes it - it only removes one instance, and if you specify an item in the list which doesn't exist, no error is thrown. The method returns true if it was able to remove an item and false if it wasn't.

The RemoveAt() method takes advantage of the fact that the generic list is index based by allowing you to remove an item based on its index/position in the list. For instance, you could remove the first item from the list like this:

listOfNames.RemoveAt(0);

Or the last item in the list like this:

listOfNames.RemoveAt(listOfNames.Count - 1);

Again, this only removes a single item and this time, you should be careful when providing the index of the item to be removed - if you use an index that falls out of bounds (lower than 0 or higher than the amount of items) an exception will be thrown! So, unless you're sure of what you're doing, you might want to wrap the RemoveAt() method in a try-catch block for handling the exception (explained in detail elsewhere in this tutorial). The RemoveAt() method doesn't return anything, so you will have to check the amount of items in the list before and after the call, to decide if it was successful - on the other hand, if you know that you have an index that exists in the list, which you should always make sure of, then you can always expect RemoveAt() to be successful.

The RemoveAll() is the most complex of the remove-methods, but definitely also the most powerful. It takes a delegate to a method as its parameter and this method then decides whether an item should be removed or not by returning true or false. This allows you to apply your own logic when removing items and it also allows you to remove more than one item at a time. Delegates will be treated elsewhere in this tutorial, because it's a big and complex subject, but I still want you to get a feel of how cool the RemoveAll method is, so here's an example:

List<string> listOfNames = new List<string>()
{
    "John Doe",
    "Jane Doe",
    "Joe Doe",
    "Another Doe"
};

listOfNames.RemoveAll(name =>
{
    if (name.StartsWith("J"))
return true;
    else
return false;
});

In this example, we use an anonymous method (again too complex to be explained here, but it will be treated in another chapter) as a parameter for the RemoveAll method. Our anonymous method is pretty simple - it will be called for each item in the list and have a parameter called name, which is of course the current item in the iteration. It looks at this name and if it starts with "J", true is returned - otherwise false. The RemoveAll() method uses this response (true or false) to decide if each item should be removed or not. In the end, this leaves our initial list with just one Doe member left: Another Doe.

Sorting List Items

So far, the items in the list we have worked with has just been used in the order in which they were added to the list. However, you may want to have the items sorted in a specific way, e.g. alphabetically in the case of our list-of-names. The List<T> has a Sort() method which we can use for this:

List<string> listOfNames = new List<string>()
{
    "John Doe",
    "Jane Doe",
    "Joe Doe",
    "Another Doe"
};
listOfNames.Sort();
foreach (string name in listOfNames)
    Console.WriteLine(name);

As you will see from the output, the items in the list have now been sorted alphabetically, and if you want it in descending order instead (from Z to A), simply call the Reverse() method after you perform the sort:

listOfNames.Sort();
listOfNames.Reverse();

So sorting a list was pretty easy, right? Well, it was mainly so easy because we have a list of strings and the .NET framework knows exactly how to compare two strings. If you have a list of numbers, .NET will, of course, know how to sort that as well. On the other hand, you might have a list of custom objects (since the List<T> can contain any object) which .NET doesn't have a chance of knowing how to compare. There are several solutions to this problem, e.g. implementing the IComparable interface or using LINQ (we'll look into both later in this tutorial), but as a quick-fix, we can also just provide a method for the Sort() method to call, to learn how two items stack up against each other, like this:

using System;
using System.Collections.Generic;

namespace ListSort
{
    class Program
    {
static void Main(string[] args)
{
    List<User> listOfUsers = new List<User>()
    {
new User() { Name = "John Doe", Age = 42 },
new User() { Name = "Jane Doe", Age = 39 },
new User() { Name = "Joe Doe", Age = 13 },
    };
    listOfUsers.Sort(CompareUsers);
    foreach (User user in listOfUsers)
Console.WriteLine(user.Name + ": " + user.Age + " years old");
}

public static int CompareUsers(User user1, User user2)
{
    return user1.Age.CompareTo(user2.Age);
}
    }

    public class User
    {
public string Name { get; set; }
public int Age { get; set; }
    }
}

This added quite a bit of code to our example, but it's actually not too complicated. If we start from the bottom, I have created a very simple User class, consisting of a name and an age. In the middle, I have declared a method called CompareUsers() - it takes two users as parameters and then returns an integer, which will indicate whether one item is "smaller", "the same" or "larger" (-1, 0 or 1). These values will be used by the Sort() method to move the items around so that the order of items will match what we want. In this case, I simply use the Age property for comparison, essentially leaving us with a list of users sorted by their age.

Summary

This article is one of the longer ones in this tutorial, but hopefully you learned a lot about lists, because the more programming you do, the more you will realize how important lists and dictionaries are. Speaking of dictionaries, we'll discuss them in the next article.


This article has been fully translated into the following languages: Is your preferred language not on the list? Click here to help us translate this article into your language!