Chapter 20: Collections and Generics in Depth
We already saw the basics of collections earlier, but now we’re going deep into the most important generic collections you’ll use every single day in real-world C# programming.
We’ll cover:
- List<T> (dynamic array – most common)
- Dictionary<TKey, TValue> (fast key-value lookups)
- HashSet<T> (unique items, super-fast checks)
- Queue<T> (FIFO – first in, first out)
- Stack<T> (LIFO – last in, first out)
- Sorted collections (SortedList, SortedDictionary, SortedSet)
- Custom comparers (when you want to sort things your own way)
I’m going to explain everything very slowly, step by step, with tons of real-life examples, clear analogies, and practical mini-projects — just like we’re sitting together in Hyderabad looking at the same screen. Let’s dive in! 🚀
1. Quick Recap: Why Generic Collections?
- Generic → type-safe (List<int> won’t let you add strings)
- Dynamic → grow/shrink automatically (unlike arrays)
- Powerful methods → .Add(), .Remove(), .Contains(), .FindAll(), .Sort()…
- All live in System.Collections.Generic namespace
Important: Always use using System.Collections.Generic; at the top of your file!
2. List<T> – The King of Collections (Dynamic Array)
When to use: You want an ordered, growable list of items (like a shopping cart, list of students, recent messages…)
Key methods:
| Method/Property | What it does | Example |
|---|---|---|
| .Add(item) | Add to the end | friends.Add(“Rahul”); |
| .Insert(index, item) | Insert at specific position | friends.Insert(0, “Priya”); |
| .Remove(item) | Remove first occurrence | friends.Remove(“Amit”); |
| .RemoveAt(index) | Remove by index | friends.RemoveAt(2); |
| .Contains(item) | Check if exists | if (friends.Contains(“Sneha”)) |
| .IndexOf(item) | Get position (-1 if not found) | int pos = friends.IndexOf(“Vikram”); |
| .Count | Number of items | friends.Count |
| .Clear() | Remove everything | friends.Clear(); |
| .Sort() | Sort (needs IComparable or custom comparer) | friends.Sort(); |
Real example – Student list manager
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
List<Student> students = new List<Student> { new Student { Name = "Rahul", Age = 22 }, new Student { Name = "Priya", Age = 21 }, new Student { Name = "Amit", Age = 24 } }; students.Add(new Student { Name = "Sneha", Age = 23 }); // Insert at beginning students.Insert(0, new Student { Name = "Vikram", Age = 20 }); // Remove by name students.RemoveAll(s => s.Name == "Amit"); // Sort by age students.Sort((a, b) => a.Age.CompareTo(b.Age)); Console.WriteLine("Students sorted by age:"); foreach (var s in students) { Console.WriteLine($"{s.Name}, {s.Age} years old"); } |
3. Dictionary<TKey, TValue> – Super-Fast Lookup by Key
When to use: You want to look up values by a unique key very quickly (phone book, user ID → user data, product code → price…)
Key points:
- Keys must be unique
- Keys cannot be null (values can)
- Very fast lookup: O(1) average time
Key methods:
| Method/Property | What it does | Example |
|---|---|---|
| .Add(key, value) | Add key-value pair | users.Add(101, “Rahul”); |
| [key] = value | Set or overwrite | users[101] = “Priya”; |
| .TryGetValue(key, out value) | Safe get (no exception) | users.TryGetValue(999, out string name) |
| .ContainsKey(key) | Check if key exists | if (users.ContainsKey(101)) |
| .Remove(key) | Remove by key | users.Remove(101); |
| .Keys / .Values | Get all keys or values | foreach (var k in users.Keys) |
| .Count | Number of pairs | users.Count |
Real example – Phone book
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
Dictionary<string, string> phoneBook = new Dictionary<string, string>(); phoneBook["Rahul"] = "9876543210"; phoneBook["Priya"] = "9123456789"; phoneBook["Sneha"] = "9988776655"; // Add or update phoneBook["Rahul"] = "9998887776"; // Updates // Safe lookup if (phoneBook.TryGetValue("Amit", out string number)) { Console.WriteLine($"Amit: {number}"); } else { Console.WriteLine("Amit not found"); } // Loop foreach (var pair in phoneBook) { Console.WriteLine($"{pair.Key}: {pair.Value}"); } |
4. HashSet<T> – Unique Items & Super-Fast Contains
When to use: You want a collection of unique items and very fast checks if something exists (no duplicates allowed)
Key methods:
| Method | What it does | Example |
|---|---|---|
| .Add(item) | Add if not already present | ids.Add(101); |
| .Contains(item) | Check existence (very fast!) | ids.Contains(101) |
| .Remove(item) | Remove if exists | ids.Remove(999); |
| .UnionWith(other) | Combine two sets | set1.UnionWith(set2); |
| .IntersectWith(other) | Keep only common items | set1.IntersectWith(set2); |
Real example – Unique user IDs
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
HashSet<int> uniqueVisitors = new HashSet<int>(); uniqueVisitors.Add(101); uniqueVisitors.Add(102); uniqueVisitors.Add(101); // Ignored – already exists! Console.WriteLine($"Unique visitors: {uniqueVisitors.Count}"); // 2 if (uniqueVisitors.Contains(102)) { Console.WriteLine("User 102 visited today!"); } |
5. Queue<T> – First In, First Out (FIFO)
When to use: Tasks that must be processed in order (print jobs, customer queue, message queue…)
Key methods:
| Method | What it does | Example |
|---|---|---|
| .Enqueue(item) | Add to the end | queue.Enqueue(“Task 1”); |
| .Dequeue() | Remove and return from front | string task = queue.Dequeue(); |
| .Peek() | Look at front without removing | string next = queue.Peek(); |
| .Count | Number of items | queue.Count |
Example – Customer service queue
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Queue<string> customerQueue = new Queue<string>(); customerQueue.Enqueue("Rahul"); customerQueue.Enqueue("Priya"); customerQueue.Enqueue("Amit"); Console.WriteLine($"Next customer: {customerQueue.Peek()}"); // Rahul string served = customerQueue.Dequeue(); Console.WriteLine($"Served: {served}"); // Rahul Console.WriteLine($"Remaining: {customerQueue.Count}"); // 2 |
6. Stack<T> – Last In, First Out (LIFO)
When to use: Undo/redo, browser back button, function call stack, parsing expressions…
Key methods:
| Method | What it does | Example |
|---|---|---|
| .Push(item) | Add to top | stack.Push(“Page 1”); |
| .Pop() | Remove and return from top | string page = stack.Pop(); |
| .Peek() | Look at top without removing | string top = stack.Peek(); |
Example – Browser history (back button)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Stack<string> history = new Stack<string>(); history.Push("Home"); history.Push("Products"); history.Push("Contact Us"); Console.WriteLine($"Current page: {history.Peek()}"); // Contact Us string previous = history.Pop(); Console.WriteLine($"Went back to: {previous}"); // Contact Us Console.WriteLine($"Now on: {history.Peek()}"); // Products |
7. Sorted Collections – Always Keep Items Sorted
| Collection | Key Features | When to use |
|---|---|---|
| SortedList<TKey, TValue> | Sorted by key, like sorted Dictionary | Need sorted key-value pairs |
| SortedDictionary<TKey, TValue> | Like Dictionary but always sorted by key | Fast insert + always sorted |
| SortedSet<T> | Unique items, always sorted | Unique + sorted collection |
Example – SortedSet
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
SortedSet<string> names = new SortedSet<string>(); names.Add("Rahul"); names.Add("Sneha"); names.Add("Priya"); names.Add("Amit"); // Automatically sorted! foreach (string name in names) { Console.WriteLine(name); // Amit, Priya, Rahul, Sneha } |
8. Custom Comparers – Sort Your Way!
By default, sorting uses IComparable (numbers, strings, DateTime…).
For custom objects or custom order → create a comparer.
Example – Sort Students by Marks descending
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class StudentComparer : IComparer<Student> { public int Compare(Student x, Student y) { // Sort by Marks descending, then by Name ascending int result = y.Marks.CompareTo(x.Marks); if (result == 0) return x.Name.CompareTo(y.Name); return result; } } // Usage students.Sort(new StudentComparer()); foreach (var s in students) { Console.WriteLine($"{s.Name}: {s.Marks}%"); } |
Mini-Project: Library Management System
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
// Book class class Book { public string Title { get; set; } public string Author { get; set; } public int Year { get; set; } public string Genre { get; set; } } // Main List<Book> library = new List<Book> { new Book { Title = "The Alchemist", Author = "Paulo Coelho", Year = 1988, Genre = "Fiction" }, new Book { Title = "Atomic Habits", Author = "James Clear", Year = 2018, Genre = "Self-Help" }, new Book { Title = "Clean Code", Author = "Robert C. Martin", Year = 2008, Genre = "Programming" }, new Book { Title = "Ikigai", Author = "Héctor García", Year = 2016, Genre = "Self-Help" } }; // 1. Dictionary: Title → Book Dictionary<string, Book> bookByTitle = library.ToDictionary(b => b.Title); // 2. HashSet: Unique authors HashSet<string> authors = new HashSet<string>(library.Select(b => b.Author)); // 3. Queue: Books to read Queue<Book> toRead = new Queue<Book>(library); // 4. SortedSet: Sorted by year SortedSet<Book> sortedByYear = new SortedSet<Book>(library, Comparer<Book>.Create((a, b) => a.Year.CompareTo(b.Year))); // 5. Group by genre var booksByGenre = library .GroupBy(b => b.Genre) .Select(g => new { Genre = g.Key, Count = g.Count(), Titles = string.Join(", ", g.Select(b => b.Title)) }); // Output Console.WriteLine("Books by genre:"); foreach (var g in booksByGenre) { Console.WriteLine($"{g.Genre} ({g.Count}): {g.Titles}"); } |
Summary – What We Learned Today
- List<T> → ordered, growable, most common
- Dictionary<TKey, TValue> → fast lookup by key
- HashSet<T> → unique items, fast .Contains()
- Queue<T> → FIFO (first in first out)
- Stack<T> → LIFO (last in first out)
- Sorted collections → SortedList, SortedDictionary, SortedSet
- Custom comparers → control sorting exactly how you want
Your Homework (Super Practical!)
- Create a new console project called CollectionsDeepDive
- Create a Movie Library system with:
- List<Movie> (Title, Director, Year, Genre, Rating)
- Dictionary<string, List<Movie>> → genre → list of movies
- HashSet<string> → unique directors
- SortedSet<Movie> → sorted by Rating descending (use custom comparer)
- Queue<Movie> → watchlist (add & watch next)
- Stack<Movie> → recently watched (undo watch)
- Add methods to add movie, search by genre, get top 3 rated movies, etc.
Next lesson: File I/O and Serialization – we’re going to learn how to save and load data to files and JSON!
You’re doing absolutely fantastic! 🎉 Any collection confusing? Want more examples with custom comparers or SortedSet? Just tell me — I’m right here for you! 💙
