1. Introduction
Working with collections isn't just about looping, filtering, and sorting. Very often, you need to change or rework these collections so they fit your needs: for example, take a collection of strings and turn it into a collection of numbers, remove duplicates from a list, or completely change the data structure.
You can solve all these tasks using standard collection methods, simple loops, and basic algorithms. It's a bit longer than using LINQ, but it's totally transparent and super useful for understanding how collections work under the hood.
Main ways to change collections
There are a bunch of ways to change collections, but usually you need one of these approaches:
- Modifying the contents: add, remove, or replace elements.
- Transforming elements: take the original collection and get a new one out of it — with a different structure, type, or shape (like turning a List<int> into a List<string>).
- Changing the view: rebuild the collection — for example, sort it, reverse it, or group it.
We're gonna focus on the first two groups, since sorting was covered in detail in a separate lecture.
Mutable and immutable collections: a key thing to know
Keep in mind that some collections in .NET can be freely changed (like List<T>), and others can't (IReadOnlyList<T>, arrays with readonly modifiers, etc.).
- Modification — we change the actual collection (for example, using Add, Remove, Clear, etc.).
- Transformation — we leave the original alone and get a new collection, usually by creating a new list and filling it with elements from the original collection.
2. Modifying collection contents
All collections that implement ICollection<T> give you basic methods for changing their contents. Let's check them out using a book catalog as an example.
Adding and removing elements
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
public int Year { get; set; }
}
List<Book> books = new List<Book>
{
new Book { Title = "Clean Code", Author = "Robert Martin", Year = 2008 },
new Book { Title = "CLR via C#", Author = "Jeffrey Richter", Year = 2012 }
};
// Adding a new book
books.Add(new Book { Title = "Head First C#", Author = "Andrew Stellman", Year = 2010 });
// Removing a book by condition (for example, by author)
for (int i = books.Count - 1; i >= 0; i--)
{
if (books[i].Author == "Robert Martin")
books.RemoveAt(i);
}
// Remove a specific object (if it's in the list)
Book someBook = books[0];
books.Remove(someBook);
// Clear the whole collection
books.Clear();
// Insert an element at a specific position
books.Insert(0, new Book { Title = "Pro C# 9", Author = "Andrew Troelsen", Year = 2021 });
Replacing and updating elements
Let's say someone messed up the publication year. How do you fix it?
// Find the right book in a loop and fix the year
for (int i = 0; i < books.Count; i++)
{
if (books[i].Title == "Head First C#")
{
books[i].Year = 2018; // Fixed the year
break;
}
}
3. Transforming collections
Transforming element types (like from Book to string)
Get a collection of all book titles:
List<string> bookTitles = new List<string>();
foreach (Book book in books)
{
bookTitles.Add(book.Title);
}
foreach (var title in bookTitles)
{
Console.WriteLine(title);
}
Get all the years from the books:
List<int> bookYears = new List<int>();
foreach (Book book in books)
{
bookYears.Add(book.Year);
}
Transform into a new type/structure:
public class BriefBookInfo
{
public string Title;
public int Year;
}
List<BriefBookInfo> briefInfos = new List<BriefBookInfo>();
foreach (Book book in books)
{
briefInfos.Add(new BriefBookInfo { Title = book.Title, Year = book.Year });
}
foreach (var info in briefInfos)
{
Console.WriteLine($"{info.Title} ({info.Year})");
}
Flattening a collection of collections (flat map)
If a book has a list of tags, get a single list of all tags:
public class Book
{
public string Title { get; set; }
public List<string> Tags { get; set; }
}
List<Book> booksWithTags = new List<Book>
{
new Book { Title = "Clean Code", Tags = new List<string> { "Clean Code", "Refactoring" } },
new Book { Title = "CLR via C#", Tags = new List<string> { "CLR", "Internals" } }
};
List<string> allTags = new List<string>();
foreach (var book in booksWithTags)
{
foreach (var tag in book.Tags)
{
allTags.Add(tag);
}
}
foreach (string tag in allTags)
{
Console.WriteLine(tag);
}
4. Commonly used collection transformation methods
ToArray
To get an array from a list:
string[] bookTitlesArray = bookTitles.ToArray();
Distinct — getting rid of duplicates
Unique authors (without LINQ):
List<string> authors = new List<string>();
foreach (Book book in books)
{
if (!authors.Contains(book.Author))
authors.Add(book.Author);
}
Reverse — flipping a collection
To reverse the order:
books.Reverse(); // changes in place!
If you don't want to mess with the original:
var reversed = new List<Book>(books);
reversed.Reverse();
Sorting (see the sorting lecture)
books.Sort((a, b) => a.Year.CompareTo(b.Year)); // sort by year
Grouping by field (GroupBy emulation)
Group books by author (get a dictionary "author — list of books"):
Dictionary<string, List<Book>> booksByAuthor = new Dictionary<string, List<Book>>();
foreach (Book book in books)
{
if (!booksByAuthor.ContainsKey(book.Author))
booksByAuthor[book.Author] = new List<Book>();
booksByAuthor[book.Author].Add(book);
}
foreach (var pair in booksByAuthor)
{
Console.WriteLine($"Author: {pair.Key}");
foreach (var b in pair.Value)
Console.WriteLine($" {b.Title}");
}
5. Handy tips
- Transformation operations using loops always work, everywhere.
- To get rid of duplicates, use temp collections and the Contains/Add methods.
- Don't change a collection while looping through it with foreach! If you need to remove a bunch of elements, collect them in a separate list first.
6. Typical tasks examples
Get a list of books from a certain year
List<Book> recentBooks = new List<Book>();
foreach (Book book in books)
{
if (book.Year > 2010)
recentBooks.Add(book);
}
foreach (var book in recentBooks)
{
Console.WriteLine($"{book.Title} ({book.Year})");
}
Print all unique publication years in ascending order
List<int> years = new List<int>();
foreach (Book book in books)
{
if (!years.Contains(book.Year))
years.Add(book.Year);
}
years.Sort();
foreach (int year in years)
Console.WriteLine(year);
Prepare books for export (create a DTO list)
public class BookExport
{
public string Name;
public string Writer;
public int PublishedYear;
}
List<BookExport> booksForExport = new List<BookExport>();
foreach (Book book in books)
{
booksForExport.Add(new BookExport
{
Name = book.Title,
Writer = book.Author,
PublishedYear = book.Year
});
}
7. Typical mistakes and gotchas
Methods like Add, Remove, Clear, Insert and Sort change the collection in place.
If you need a new collection — make a new list and add the elements you want there.
When removing elements while looping through a collection, use a backwards loop (for (int i = Count-1; i >= 0; i--)).
If you need a list with unique elements, watch out for repeats: use Contains or HashSet<T>.
GO TO FULL VERSION