Not that long ago, we published a post about the fundamentals of the C# array. Today’s post will continue the trend, covering the C# list.
Don’t worry.? If you’re a beginner, you’ll also benefit from this post. Instead of brushing up, you’ll get your first contact with this incredibly useful data structure.
As in the array post, we?ll discuss what a list is. We?ll learn how to use it, what its most common operations are, and how to avoid some common pitfalls. With that in mind, you’re ready to see what the C# list has to offer you.
C# List: Definition and Characteristics
In order to use lists in C#, you need to understand what they are, how they work, what they’re good for, and what they’re not.
First of all, let’s set the terminology straight. What we call a C# list throughout this post is actually the List<T> class from the .Net BCL?(Base Class Library), which lives in the System.Collections.Generic namespace.
The Microsoft Developer Network (MSDN) documentation says this about the list class:
[It] represents a strongly typed list of objects that can be accessed by index. Provides methods to search, sort, and manipulate lists.
Let’s break down this quote.
The C# List Is Strongly Typed
You can define that a given list is of a specific type, and then the compiler will guarantee that only elements of that type can be added to the list:
List<string> languages = new List<string>(); // defining the type of the list with a generic type parameter languages.Add("C#"); languages.Add("F#"); languages.Add("Kotlin"); languages.Add(10); // compilation error!
Since List<T> uses generics, you can create a list of any type you want and know that the compiler will protect you from wrong types. Compare this approach to that of, let’s say, ArrayList:
ArrayList listOfIntegers = new ArrayList(); listOfIntegers.Add(10); listOfIntegers.Add(15); listOfIntegers.Add(true); listOfIntegers.Add("this wasn't supposed to be here!");
Even though the developer intended for the list to be used just for integers, there’s no way for the compiler to enforce this rule. So, the ArrayList approach has these problems:
- It won’t protect you from developers trying to add elements from the wrong type.
- It’s not as self-documenting.
- It can harm performance since it will require boxing and unboxing when working with value types.
One thing to note: you are allowed to add values of a subtype to a list of its supertype. So, for example, the following code is 100 percent valid if Employee inherits from Person:
var roster = new List<Person>(); roster.Add(new Employee("Joe", "Smith"));
The C# List Is Indexed
Just like with an array, you can retrieve an item by providing its index.
The C# List Is Mutable
The C# list is mutable. This means you can easily add or remove elements when you need to. We’ll see just how to do that in a minute.
Creating a List in C#
Let’s cover some of the most common ways you can create/populate a list in C#.
Using the Parameterless Constructor and Adding Items Later
One of the most straightforward ways to create a list is to just use the parameterless constructor and add the elements later, as needed.
var n = new List<int>(); n.Add(15); n.Add(30); n.Add(42);
In the code above, we declare a new list of int and immediately proceed to insert three items in it.
Initiating from an IEnumerable
There’s yet another version of the list constructor that takes an IEnumerable<T> as a?parameter. That allows you to instantiate a list from an array:
string[] parts = input.Split(','); List<string> list = new List<string>(parts);
Let’s say you need to convert a string to a list of char. How would you go about doing that? The following would work:
var letters = new List<char>(message.ToCharArray());
But we can simplify further. Since “string” implements IEnumerable<char>, we can can just do it like this:
var letters = new List<char>(message);
Using the Collection Initializer
A very common thing to do is to create a list that already has some items. Based on what you’ve learned in the previous section, you could write something like this:
var numbers = new List<int>(new int[] { 10, 20, 30, 50});
But there’s an easier way:
var numbers = new List<int>(){ 10, 20, 30, 50};
And to make things even faster, the parentheses are optional, which means you can get away with just this:
var numbers = new List<int> { 10, 20, 30, 50};
Nice, right?
Using the ‘ToList()’ LINQ Method
By adding the System.Linq namespace, you gain access to the ToList() extension method with which you can quickly generate a list from any IEnumerable. That’s especially handy when you need to consolidate the result from a LINQ query into a list.
Using C# Lists: The Main Operations
Now that you know the different ways you can create a list, we’re going to cover some of the main operations you’ll perform with a list.
Adding Several Elements at Once
What if you need to add several items to a list at once? More specifically: what if you need to insert the elements from one list into another? That’s what the AddRange method is for:
List<int> myList = new List<int> { 1, 2 ,3}; myList.AddRange(new [] {4,5,6});
AddRange takes an IEnumerable<T> as a parameter. So it accepts not only lists, but also arrays and any other types that implement the IEnumerable<T> interface.
Removing Elements
As we’ve mentioned, the List<T> class is a mutable list. It’s possible not only to add elements to it, but also to remove them.
How do you go about that? With the help of these four methods: Remove, RemoveAt, RemoveAll, and RemoveRange.
Remove
Let’s start with Remove. This method takes a parameter, which is of the same type as the list’s type parameter.
In other words, if the list is a list of int, then this method receives an int. The method then proceeds to remove the first item from the list that is equal to the parameter’s value.
It returns true if it succeeds in removing the item and false otherwise. If the item is not on the list, the method will also return false.
RemoveAt
The next method on our list is RemoveAt. This method takes an int as a parameter, which represents the zero-based index of the element to be removed. Easy peasy.
Be aware that this method will throw an ArgumentOutOfRangeReception if you give it an index that is a) negative, or b) greater than or equal to the length of the list.
RemoveAll
RemoveAll removes all the items that match with the specified criteria it takes as a parameter in the form of a Predicate delegate. Let’s see a quick example:
var numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; numbers.RemoveAll(x => x > 5); // removing the numbers greater than 5 Console.WriteLine(string.Join(", ", numbers)); // will print "1, 2, 3, 4, 5"
RemoveRange
The RemoveRange method is also very easy to understand and use. It takes two ints as the parameters: the first represents an index, and the second represents how many items you wish to remove.
So, a call like list.RemoveRange(10, 15) should be read as “starting at index 10, remove 15 items.” If you provide an invalid index or count, you’ll get an exception, so pay attention to this.
Clear
Finally, it’s time to bring out the big guns. What if you want to remove all the elements in one swap? That’s what the Clear method is for. It doesn’t take any parameter and it doesn’t return anything. It’s easy as that.
Accessing the Elements
So far, we’ve covered how to create a list and add elements to it. We’ve also talked about the different ways to remove items from the list you have at your disposal.
But a list (or any type of collection) isn’t of much use if you can’t access the elements themselves, right? So how do you go about that?
As we’ve already mentioned, you can grab an element by its index, just like you’d do with an array. This means that you can easily iterate through the list items with a for loop.
But using foreach is more declarative and probably more appropriate for most situations:
List<string> names = GetAListOfNamesFromSomewhere(); foreach (string name in names) { // do something with the name }
What if you need to get a subset of a list?
The GetRange method has your back. It takes two integers as the parameters:
- The zero-based index of the first of the elements you wish to retrieve.
- The number of elements you want to get.
The same rule applies here in regards to index: if you pass something invalid, you’ll get an exception.
Working With the C# List:?The Dos and Don’ts
We’ve gone over the definition of a C# list and several operations that you’d commonly perform with one. Now to conclude, let’s quickly cover some best practices and pitfalls you should be aware of while working with a C# list. Make sure that you:
- DO use a list when you don’t know from the beginning how many items your collection will be holding.
- DO opt for a foreach over a for loop unless you’ve a good reason for not doing so. Code clarity trumps micro-optimizations more often than not.
- DO be careful when supplying an index to a method or the indexer; you will an exception if the index you pass is not valid.
- DO NOT?use a C# list if you want to prevent the consumer of your code from messing with its elements. Remember: the list is mutable!
- CONSIDER the possibility of a given item being null when iterating over a list of a reference type.
What?s Next?
This post was meant to give you a taste of what the C# list is all about and what you can do with it, but it’s not exhaustive by any means. We didn’t cover sorting or searching, for instance.
That’s why you shouldn’t stop here. Read both documentation and code. Teach others. And most importantly: practice a lot. That’s the only path to becoming a great software developer.
You might also be interested in further reading about other C# language concepts:
Learn more how CodeIt.Right can help you automate code reviews and improve the quality of your code.
2 Comments. Leave new
[…] C# List: Definition, Examples, Best Practices, and Pitfalls – Carlos Schults […]
Nice write up but I would also add initializing a list with a given start size and also using IReadOnlyList to make it non-mutable.