Lists
List<T> is the collection you'll use more than any other. Like an array it keeps items in order and gives fast access by index, but it grows and shrinks as you add and remove items. The <T> means it's generic: List<string> holds strings, List<int> holds integers, and the compiler checks every item.
Creating a list and adding items
var tasks = new List<string>(); tasks.Add("Write report"); tasks.Add("Email Sam"); tasks.Add("Book flights"); List<int> primes = [2, 3, 5, 7]; // collection expression List<string> copy = new(tasks); // copy of another collection Console.WriteLine(tasks.Count); Console.WriteLine(tasks[0]); Console.WriteLine(string.Join(", ", primes)); Console.WriteLine(copy.Count);
3 Write report 2, 3, 5, 7 3
Lists use Count, not Length. new(tasks) is a target-typed new: the type comes from the variable declaration.
Adding, inserting and removing
List<string> queue = ["Ana", "Ben", "Cleo"]; queue.Insert(0, "VIP Dan"); // at a position queue.AddRange(["Eve", "Finn"]); // several at once Print(queue); queue.Remove("Ben"); // first matching value queue.RemoveAt(0); // by index Print(queue); bool removed = queue.Remove("Zoe"); // returns false if not found Console.WriteLine(removed); queue.RemoveAll(name => name.StartsWith('E') || name.StartsWith('F')); Print(queue); queue.Clear(); Console.WriteLine(queue.Count); static void Print(List<string> list) => Console.WriteLine($"[{string.Join(", ", list)}]");
[VIP Dan, Ana, Ben, Cleo, Eve, Finn] [Ana, Cleo, Eve, Finn] False [Ana, Cleo] 0
Searching
List<int> temps = [18, 22, 25, 19, 30, 22]; Console.WriteLine(temps.Contains(25)); Console.WriteLine(temps.IndexOf(22)); Console.WriteLine(temps.LastIndexOf(22)); Console.WriteLine(temps.Find(t => t > 20)); Console.WriteLine(temps.FindIndex(t => t > 28)); Console.WriteLine(string.Join(", ", temps.FindAll(t => t >= 22))); Console.WriteLine(temps.Exists(t => t < 0)); Console.WriteLine(temps.TrueForAll(t => t > 10));
True 1 5 22 4 22, 25, 30, 22 False True
Sorting
Sort() sorts in place using the natural order. Pass a comparison to sort any other way — a.CompareTo(b) for ascending, b.CompareTo(a) for descending:
List<string> words = ["pear", "Fig", "banana", "kiwi", "apple"]; words.Sort(); Console.WriteLine(string.Join(", ", words)); words.Sort((a, b) => a.Length.CompareTo(b.Length)); Console.WriteLine(string.Join(", ", words)); words.Sort((a, b) => b.CompareTo(a)); Console.WriteLine(string.Join(", ", words)); words.Reverse(); Console.WriteLine(string.Join(", ", words));
apple, banana, Fig, kiwi, pear Fig, kiwi, pear, apple, banana pear, kiwi, Fig, banana, apple apple, banana, Fig, kiwi, pear
The default string sort is culture-aware, so "Fig" sorts with the other F-words rather than before all lower-case letters. LINQ's OrderBy (lesson 49) sorts into a new sequence and leaves the list unchanged.
Lists of objects
Lists usually hold objects. Here a record (lesson 40) defines a small product type:
List<Product> cart = [ new("Laptop stand", 34.99m, 1), new("USB-C cable", 9.99m, 3), new("Webcam", 59.00m, 1), ]; cart.Add(new Product("Mouse pad", 7.50m, 2)); decimal total = 0; foreach (Product p in cart) { Console.WriteLine($"{p.Name,-14}{p.Quantity} x {p.Price,6}"); total += p.Price * p.Quantity; } Console.WriteLine($"Total: {total}"); record Product(string Name, decimal Price, int Quantity);
Laptop stand 1 x 34.99 USB-C cable 3 x 9.99 Webcam 1 x 59.00 Mouse pad 2 x 7.50 Total: 138.96
Don't modify a list while looping over it
Adding or removing items inside a foreach over the same list throws an exception, because the loop can no longer keep its place:
List<int> numbers = [1, 2, 3, 4, 5, 6]; foreach (int n in numbers) { if (n % 2 == 0) numbers.Remove(n); }
Unhandled exception. System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
Use RemoveAll with a condition, loop backwards with a for loop, or build a new list instead:
List<int> numbers = [1, 2, 3, 4, 5, 6]; numbers.RemoveAll(n => n % 2 == 0); Console.WriteLine(string.Join(", ", numbers)); List<int> more = [1, 2, 3, 4, 5, 6]; for (int i = more.Count - 1; i >= 0; i--) { if (more[i] % 3 == 0) more.RemoveAt(i); } Console.WriteLine(string.Join(", ", more));
1, 3, 5 1, 2, 4, 5
Count and Capacity
Internally a list stores items in an array. When it fills up, the list allocates a bigger array (roughly double the size) and copies everything across. Capacity is the size of that internal array; Count is how many items are actually stored:
var list = new List<int>(); int lastCapacity = -1; for (int i = 0; i < 20; i++) { list.Add(i); if (list.Capacity != lastCapacity) { Console.WriteLine($"Count {list.Count,2} -> capacity {list.Capacity}"); lastCapacity = list.Capacity; } }
Count 1 -> capacity 4 Count 5 -> capacity 8 Count 9 -> capacity 16 Count 17 -> capacity 32
If you know roughly how many items are coming, new List<int>(10_000) sets the starting capacity and avoids the repeated copying.
Converting and combining
List<int> a = [1, 2, 3]; int[] b = [4, 5]; int[] asArray = a.ToArray(); List<int> asList = b.ToList(); List<int> combined = [.. a, .. b, 6]; // spread into a new list Console.WriteLine(asArray.Length); Console.WriteLine(asList.Count); Console.WriteLine(string.Join(", ", combined)); Console.WriteLine(string.Join(", ", combined.GetRange(1, 3)));
3 2 1, 2, 3, 4, 5, 6 2, 3, 4
Try it yourself
Build a simple to-do list: add tasks, mark one done by removing it, and print what's left in alphabetical order.
4 tasks left: 1. Buy milk 2. Call mum 3. Renew passport 4. Water plants