1. Введение
Работа с коллекциями — это не только перебор, фильтрация и сортировка. Очень часто возникает задача изменить или переработать эти коллекции, чтобы они соответствовали вашим нуждам: например, взять коллекцию строк и превратить её в коллекцию чисел, удалить из списка дубликаты или полностью поменять структуру данных.
Все эти задачи можно решить с помощью стандартных методов коллекций, простых циклов и базовых алгоритмов. Это немного длиннее, чем с помощью LINQ, но зато абсолютно прозрачно и полезно для понимания принципов работы коллекций.
Основные способы изменения коллекций
Изменять коллекции можно по-разному, и чаще всего нужен один из следующих подходов:
- Модификация содержимого: добавить, удалить или заменить элементы.
- Преобразование элементов: взять исходную коллекцию и получить на её основе новую — другой структуры, типа или формы (например, из List<int> сделать List<string>).
- Изменение представления: перестроить коллекцию — например, отсортировать, развернуть (reverse) или сгруппировать.
Мы сфокусируемся на первой и второй группе, потому что сортировка подробно рассматривалась в отдельной лекции.
Изменяемые и неизменяемые коллекции: важный нюанс
Стоит помнить, что некоторые коллекции в .NET можно свободно изменять (например, List<T>), а другие — нельзя (IReadOnlyList<T>, массивы с модификаторами только для чтения и т.д.).
- Модификация — меняем конкретную коллекцию (например, через Add, Remove, Clear и т.д.).
- Преобразование — не трогаем оригинал, а получаем новую коллекцию, обычно через создание нового списка и заполнения его по элементам исходной коллекции.
2. Модификация содержимого коллекций
Все коллекции, реализующие ICollection<T>, предоставляют базовые методы для изменения содержимого. Рассмотрим их на примере каталога книг.
Добавление и удаление элементов
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 = "Чистый код", Author = "Роберт Мартин", Year = 2008 },
new Book { Title = "CLR via C#", Author = "Джеффри Рихтер", Year = 2012 }
};
// Добавление новой книги
books.Add(new Book { Title = "Head First C#", Author = "Эндрю Стеллман", Year = 2010 });
// Удаление книги по условию (например, по автору)
for (int i = books.Count - 1; i >= 0; i--)
{
if (books[i].Author == "Роберт Мартин")
books.RemoveAt(i);
}
// Удалить конкретный объект (если он есть в списке)
Book someBook = books[0];
books.Remove(someBook);
// Очистить всю коллекцию
books.Clear();
// Вставить элемент в определённую позицию
books.Insert(0, new Book { Title = "Pro C# 9", Author = "Эндрю Троелсен", Year = 2021 });
Замена и обновление элементов
Допустим, ошибся кто-то с годом выпуска. Как это исправить?
// Найдём нужную книгу через цикл и исправим год
for (int i = 0; i < books.Count; i++)
{
if (books[i].Title == "Head First C#")
{
books[i].Year = 2018; // Исправили год
break;
}
}
3. Преобразование коллекций
Преобразование типа элементов (например, из Book в строку)
Получить коллекцию названий всех книг:
List<string> bookTitles = new List<string>();
foreach (Book book in books)
{
bookTitles.Add(book.Title);
}
foreach (var title in bookTitles)
{
Console.WriteLine(title);
}
Получить годы всех книг:
List<int> bookYears = new List<int>();
foreach (Book book in books)
{
bookYears.Add(book.Year);
}
Преобразование в новый тип/структуру:
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})");
}
Преобразование коллекции коллекций в одну (flat map)
Если у книги есть список тегов, получить единый список всех тегов:
public class Book
{
public string Title { get; set; }
public List<string> Tags { get; set; }
}
List<Book> booksWithTags = new List<Book>
{
new Book { Title = "Чистый код", 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. Часто используемые методы преобразования коллекций
ToArray
Чтобы получить массив из списка:
string[] bookTitlesArray = bookTitles.ToArray();
Distinct — избавляемся от дубликатов
Уникальные авторы (без LINQ):
List<string> authors = new List<string>();
foreach (Book book in books)
{
if (!authors.Contains(book.Author))
authors.Add(book.Author);
}
Reverse — переворачиваем коллекцию
Чтобы изменить порядок на обратный:
books.Reverse(); // изменяет на месте!
Если не хотите менять оригинал:
var reversed = new List<Book>(books);
reversed.Reverse();
Сортировка (см. лекцию про сортировку)
books.Sort((a, b) => a.Year.CompareTo(b.Year)); // сортировка по году
Группировка по полю (эмуляция GroupBy)
Сгруппировать книги по автору (получить словарь "автор — список книг"):
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($"Автор: {pair.Key}");
foreach (var b in pair.Value)
Console.WriteLine($" {b.Title}");
}
5. Полезные нюансы
- Операции преобразования с помощью циклов работают всегда и везде.
- Чтобы исключить дубли, используйте временные коллекции и методы Contains/Add.
- Не меняйте коллекцию во время её перебора через foreach! Если нужно удалить много элементов, сначала соберите их в отдельный список.
6. Примеры типовых задач
Получить список книг за определённый год
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})");
}
Вывести все уникальные годы публикации по возрастанию
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);
Подготовить книги к экспорту (создать список-DTO)
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. Типовые ошибки и подводные камни
Методы типа Add, Remove, Clear, Insert и Sort изменяют коллекцию на месте.
Если вам нужна новая коллекция — создавайте новый список и добавляйте туда нужные элементы.
При удалении элементов во время обхода коллекции используйте перебор с конца (for (int i = Count-1; i >= 0; i--)).
Если нужен список с уникальными элементами, следите за повторениями: используйте Contains или HashSet<T>.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ