List<T> — lista, która rośnie razem z programem
Do koła programistycznego dopisują się kolejne osoby, a ktoś inny rezygnuje. Tablica tego nie udźwignie, bo jej długość ustala się raz. Lista zmienia rozmiar sama.
Czego się dziś nauczysz
- Utworzysz
List<T>i wyjaśnisz, co oznacza typ w nawiasach ostrych. - Dodasz, wstawisz i usuniesz element.
- Odróżnisz
Countod indeksu ostatniego elementu. - Usuniesz elementy pętlą, nie psując przy tym numeracji.
- Wskażesz sytuacje, w których lepsza jest tablica, a w których lista.
Przygotowanie: lekcje 01–30. Przewidywany czas: 45–90 minut z zadaniami. Przykłady wymagają .NET 8 lub nowszego, z włączonymi ImplicitUsings i Nullable.
Tablica ma stałą długość
Do koła programistycznego zapisuje się pierwsza osoba. Potem druga. Potem ktoś rezygnuje, a na jego miejsce przychodzą dwie kolejne.
string[] uczestnicy = new string[3];
uczestnicy[0] = "Ala";
uczestnicy[1] = "Bartek";
uczestnicy[2] = "Cezary";
// uczestnicy[3] = "Dorota"; // IndexOutOfRangeException
Tablica z lekcji 23 ma długość ustaloną w chwili utworzenia. Dopisanie czwartej osoby wymaga utworzenia nowej tablicy i przepisania zawartości — Array.Resize robi dokładnie to, tylko w ukryciu. A usunięcie kogoś ze środka zostawia dziurę, którą trzeba zasypać ręcznie.
List<T> zajmuje się tym za ciebie:
List<string> uczestnicy = new List<string>();
uczestnicy.Add("Ala");
uczestnicy.Add("Bartek");
uczestnicy.Add("Cezary");
uczestnicy.Add("Dorota"); // lista sama sie powieksza
uczestnicy.Remove("Bartek"); // i sama sie zsuwa
Console.WriteLine(uczestnicy.Count); // 3
Console.WriteLine(uczestnicy[1]); // Cezary
string[] | List<string> | |
|---|---|---|
| Rozmiar | ustalony przy tworzeniu | zmienia się w trakcie |
| Dodanie elementu | trzeba tworzyć nową tablicę | Add |
| Usunięcie elementu | ręczne przesuwanie | Remove, RemoveAt |
| Liczba elementów | Length | Count |
| Dostęp po indeksie | t[0] | lista[0] — tak samo |
| Kiedy wybrać | liczba elementów znana i stała | liczba elementów zmienia się w trakcie |
Co oznacza T w nawiasach ostrych
Zapis List<string> czytamy: „lista napisów”. Litera w nawiasach ostrych mówi, jakiego typu elementy lista przechowuje — i kompilator tego pilnuje.
List<string> imiona = new List<string>();
List<int> punkty = new List<int>();
List<double> pomiary = new List<double>();
imiona.Add("Ala");
// imiona.Add(42); // BLAD CS1503: nie mozna zamienic int na string
Dzięki temu do listy imion nikt nie doda liczby przez pomyłkę — błąd wychodzi przy kompilacji, a nie u użytkownika. Mechanizm, który za tym stoi, poznasz dokładniej w lekcji 52 przy generykach; na razie wystarczy zasada: w nawiasach ostrych stoi typ elementu.
Trzy sposoby na utworzenie listy
// 1. pusta lista
List<string> a = new List<string>();
// 2. lista z wartosciami poczatkowymi
List<string> b = new List<string> { "Ala", "Bartek", "Cezary" };
// 3. lista z zawartosci tablicy
string[] tablica = { "Ala", "Bartek" };
List<string> c = new List<string>(tablica);
Zapis skrócony
Od C# 9 wolno napisać List<string> lista = new(); — typ po prawej stronie jest oczywisty, więc kompilator go uzupełnia. W kursie piszemy pełną formę, żeby było widać, co powstaje, ale w cudzym kodzie spotkasz obie.
Dodawanie, usuwanie, sprawdzanie
List<string> lista = new List<string> { "Ala", "Bartek", "Cezary" };
lista.Add("Dorota"); // na koniec
lista.Insert(1, "Ewa"); // na pozycje 1, reszta przesuwa sie w prawo
Console.WriteLine(lista.Count); // 5
Console.WriteLine(lista.Contains("Ala")); // True
Console.WriteLine(lista.IndexOf("Cezary")); // 3 (po wstawieniu Ewy!)
lista.Remove("Ewa"); // usuwa PIERWSZE wystapienie wartosci
lista.RemoveAt(0); // usuwa element o indeksie 0
Console.WriteLine(string.Join(", ", lista)); // Bartek, Cezary, Dorota
lista.Clear();
Console.WriteLine(lista.Count); // 0
Indeksy przesuwają się same
Po Insert(1, …) wszystko, co stało od pozycji 1 w prawo, dostaje indeks o jeden większy. Po RemoveAt — o jeden mniejszy. Dlatego numer indeksu zapisany w zmiennej przed zmianą listy przestaje być aktualny. To najczęstsza przyczyna „dziwnych” wyników przy usuwaniu.
Count a indeks ostatniego elementu
Ta sama zasada, co przy tablicach: indeksy zaczynają się od zera, więc ostatni element ma numer Count - 1.
List<string> lista = new List<string> { "Ala", "Bartek", "Cezary" };
Console.WriteLine(lista.Count); // 3
Console.WriteLine(lista[lista.Count - 1]); // Cezary
Console.WriteLine(lista[^1]); // Cezary - zapis skrocony
// Console.WriteLine(lista[lista.Count]); // ArgumentOutOfRangeException
Pusta lista nie ma indeksu 0
Odczyt lista[0] z pustej listy kończy się wyjątkiem ArgumentOutOfRangeException. Przed sięgnięciem po element zawsze sprawdź Count — albo użyj foreach, który po pustej liście po prostu nie wykona ani jednego obiegu.
Przechodzenie i modyfikowanie
Do samego wypisania wygodniejszy jest foreach. Gdy potrzebny jest numer pozycji albo zmiana zawartości — for z indeksem.
List<string> lista = new List<string> { "Ala", "Bartek", "Cezary" };
foreach (string imie in lista)
{
Console.WriteLine(imie);
}
for (int i = 0; i < lista.Count; i++)
{
Console.WriteLine($"{i + 1}. {lista[i]}");
}
Nie zmieniaj listy w trakcie foreach
Dodanie albo usunięcie elementu wewnątrz foreach po tej samej liście kończy się wyjątkiem InvalidOperationException w następnym obiegu: „kolekcja została zmodyfikowana”. Pętla trzyma w środku licznik pozycji, a zmiana listy czyni go nieaktualnym.
Usuwanie robimy pętlą for od końca. Dzięki temu przesunięcie indeksów dotyczy tylko elementów, które już sprawdziliśmy:
List<int> oceny = new List<int> { 5, 1, 4, 1, 3 };
for (int i = oceny.Count - 1; i >= 0; i--)
{
if (oceny[i] == 1)
{
oceny.RemoveAt(i);
}
}
Console.WriteLine(string.Join(", ", oceny)); // 5, 4, 3
Krótsza droga do tego samego
oceny.RemoveAll(o => o == 1); robi to jedną instrukcją. Zapis ze strzałką to wyrażenie lambda — poznasz je w lekcji 53. Warto jednak raz napisać wersję z pętlą, bo dopiero ona pokazuje, dlaczego idziemy od końca.
Sortowanie i odwracanie
List<string> imiona = new List<string> { "Cezary", "Ala", "Bartek" };
imiona.Sort(); // w miejscu, alfabetycznie
Console.WriteLine(string.Join(", ", imiona)); // Ala, Bartek, Cezary
imiona.Reverse();
Console.WriteLine(string.Join(", ", imiona)); // Cezary, Bartek, Ala
Sortowanie zmienia listę, nie tworzy nowej
Sort i Reverse działają w miejscu — nie zwracają nowej listy, tylko przestawiają elementy w istniejącej. Jeśli oryginalna kolejność jest jeszcze potrzebna, zrób najpierw kopię: List<string> kopia = new List<string>(imiona);.
Przykład: zapisy do koła programistycznego
using System.Collections.Generic;
class Program
{
static void Pokaz(string tytul, List<string> lista)
{
Console.WriteLine();
Console.WriteLine($"{tytul} ({lista.Count}):");
if (lista.Count == 0)
{
Console.WriteLine(" (nikt)");
return;
}
for (int i = 0; i < lista.Count; i++)
{
Console.WriteLine($" {i + 1}. {lista[i]}");
}
}
static void Main()
{
List<string> uczestnicy = new List<string>();
uczestnicy.Add("Ala");
uczestnicy.Add("Bartek");
uczestnicy.Add("Cezary");
Pokaz("Po zapisach", uczestnicy);
uczestnicy.Insert(0, "Dorota");
Pokaz("Dorota z pierwszenstwem", uczestnicy);
bool usunieto = uczestnicy.Remove("Bartek");
Console.WriteLine($"\nUsunieto Bartka: {usunieto}");
Console.WriteLine($"Usunieto Zenona: {uczestnicy.Remove("Zenon")}");
uczestnicy.Sort();
Pokaz("Po posortowaniu", uczestnicy);
Console.WriteLine();
Console.WriteLine($"Czy jest Ala: {uczestnicy.Contains("Ala")}");
Console.WriteLine($"Ala jest na pozycji: {uczestnicy.IndexOf("Ala") + 1}");
Console.WriteLine($"Ostatnia osoba: {uczestnicy[uczestnicy.Count - 1]}");
}
}
Po zapisach (3):
1. Ala
2. Bartek
3. Cezary
Dorota z pierwszenstwem (4):
1. Dorota
2. Ala
3. Bartek
4. Cezary
Usunieto Bartka: True
Usunieto Zenona: False
Po posortowaniu (3):
1. Ala
2. Cezary
3. Dorota
Czy jest Ala: True
Ala jest na pozycji: 1
Ostatnia osoba: Dorota
Co dzieje się po kolei
Insert(0, …)wstawia Dorotę na początek, a pozostałe trzy osoby dostają indeksy o jeden większe.Removezwracabool—true, gdy element znaleziono i usunięto. Próba usunięcia kogoś, kogo nie ma, nie jest błędem; po prostu zwracafalse.- Metoda
Pokazprzyjmuje listę jako parametr — działa dla każdej listy napisów. To metoda z lekcji 19, tylko z innym typem parametru. - Wcześniejsze
returnwPokazobsługuje pustą listę i chroni pętlę przed sięganiem po nieistniejący element. - Numery na ekranie to
i + 1, bo człowiek liczy od jednego, a lista od zera.
Zestawienie metod List<T>
| Metoda | Zwraca | Działanie i ograniczenia |
|---|---|---|
Add(x) | — | Dopisuje na końcu. Lista powiększa się sama. |
Insert(i, x) | — | Wstawia na pozycji i; reszta przesuwa się w prawo. Indeks większy od Count daje wyjątek. |
Remove(x) | bool | Usuwa pierwsze wystąpienie wartości. false, gdy nie znaleziono. |
RemoveAt(i) | — | Usuwa element o podanym indeksie. Poza zakresem — ArgumentOutOfRangeException. |
RemoveAll(warunek) | int | Usuwa wszystkie pasujące i zwraca ich liczbę. Warunek zapisujemy lambdą (lekcja 53). |
Clear() | — | Usuwa wszystko; Count spada do zera. |
Count | int | Liczba elementów. Właściwość — bez nawiasów. W tablicy odpowiednikiem jest Length. |
lista[i] | T | Odczyt i zapis elementu, tak jak w tablicy. Indeksy od 0 do Count - 1. |
Contains(x) | bool | Czy wartość jest na liście. Dla napisów rozróżnia wielkość liter. |
IndexOf(x) | int | Pozycja pierwszego wystąpienia albo -1. Tej wartości nie podawaj do indeksatora. |
Sort() / Reverse() | — | Porządkują listę w miejscu. Sortowanie własnych typów wymaga IComparable (lekcja 51). |
ToArray() | T[] | Kopia zawartości jako tablica — przydatne, gdy metoda oczekuje tablicy. |
string.Join(sep, lista) | string | Skleja elementy separatorem; działa dla list tak samo jak dla tablic. |
Zanim utkniesz
| Zapis | Problem |
|---|---|
lista.Length | Lista ma Count, tablica ma Length. To najczęstsza pomyłka po lekcji o tablicach. |
lista[lista.Count] | Zawsze poza zakresem. Ostatni element to lista[lista.Count - 1]. |
Dodawanie lub usuwanie w foreach | InvalidOperationException: kolekcja została zmodyfikowana. Usuwaj pętlą for od końca. |
Usuwanie pętlą for od początku | Po RemoveAt(i) następny element wskakuje na miejsce i i zostaje pominięty. Idź od końca. |
lista.Sort() zapisane jako lista = lista.Sort(); | CS0029 — Sort nic nie zwraca, bo sortuje w miejscu. |
new List<string>(5) i odczyt lista[0] | Liczba w nawiasie to zapas miejsca, a nie liczba elementów. Lista nadal jest pusta i Count wynosi 0. |
lista.IndexOf(x) bez sprawdzenia wyniku | Gdy elementu nie ma, wynikiem jest -1, a lista[-1] kończy się wyjątkiem. |
Zadania
Utwórz List<string>, dodaj pięć imion, wypisz je z numeracją od jednego i podaj liczbę osób. Nie wpisuj liczby na sztywno.
Do listy z poprzedniego zadania dopisz osobę na drugą pozycję, usuń jedną po nazwie i jedną po indeksie. Po każdej zmianie wypisz całą listę przez string.Join.
Wczytaj imię od użytkownika i sprawdź, czy jest na liście. Jeśli tak — podaj jego pozycję liczoną od jednego; jeśli nie — wypisz komunikat. Skorzystaj z IndexOf i pamiętaj o wartości -1.
Utwórz List<int> z ocenami, w tym kilkoma jedynkami. Usuń wszystkie jedynki pętlą for od końca, a potem policz i wypisz średnią z pozostałych. Zadbaj o to, żeby pusta lista nie spowodowała dzielenia przez zero.
Napisz program z menu: dodaj osobę na koniec, obsłuż pierwszą osobę (usuń ją z listy i wypisz jej imię), pokaż całą kolejkę, zakończ. Obsłuż próbę obsłużenia kogoś z pustej kolejki. Każdą operację zamknij w osobnej metodzie przyjmującej listę jako parametr.
Wczytuj imiona, dopóki użytkownik nie wpisze pustego wiersza. Imiona już obecne na liście odrzucaj z komunikatem, zamiast dodawać po raz drugi. Na koniec wypisz listę posortowaną alfabetycznie, a pod nią tę samą listę w kolejności zapisów. Podpowiedź: żeby mieć obie kolejności, potrzebne są dwie listy albo kopia przed sortowaniem.
Co trzeba zapamiętać
List<T>zmienia rozmiar w trakcie działania programu, tablica nie.- W nawiasach ostrych stoi typ elementu — kompilator pilnuje, żeby nic innego nie trafiło na listę.
- Liczbę elementów podaje
Count, a ostatni element ma indeksCount - 1. Removeusuwa pierwsze wystąpienie wartości i zwracabool;RemoveAtdziała po indeksie.- Listy nie wolno zmieniać w trakcie
foreach; usuwaj pętląforod końca. SortiReverseporządkują listę w miejscu — nie zwracają nowej.
Dokumentacja: Microsoft Learn — temat tej lekcji.