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>.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ