1. Wprowadzenie
Wyobraź sobie, że prowadzisz listę zakupów. Dziś musisz kupić 3 rzeczy, jutro — 10, a pojutrze — tylko jedną. Gdybyś używał tablicy, musiałbyś za każdym razem tworzyć nową tablicę pod konkretną liczbę rzeczy. Albo zrobić bardzo dużą tablicę "na wszelki wypadek", przez co "nosiłbyś" ze sobą mnóstwo pustych komórek. Brzmi mało efektywnie, prawda?
A teraz wyobraź sobie, że masz magiczny plecak, który sam się powiększa, gdy coś do niego wkładasz, i kurczy, gdy coś wyjmujesz. Nie musisz z góry wiedzieć, ile rzeczy włożysz. Po prostu wrzucasz, a plecak się dostosowuje. To właśnie jest List<T> w świecie programowania w C#!
List<T> — to generyczna kolekcja, która pozwala przechowywać sekwencję elementów określonego typu. Kluczowe słowo tutaj to "dynamiczna". W przeciwieństwie do tablic, List<T> może zmieniać swój rozmiar w trakcie działania programu. Sama zarządza swoją wewnętrzną pojemnością, zwiększając ją w razie potrzeby.
Co oznacza litera T w List<T>? To tzw. parametr typu. Gdy tworzysz List, podajesz, jaki typ danych będzie przechowywany zamiast T. Na przykład, List<int> będzie przechowywać tylko liczby całkowite, List<string> — tylko napisy, a List<double> — tylko liczby zmiennoprzecinkowe. To gwarantuje, że przypadkiem nie wrzucisz jabłka do listy pomarańczy, co jest bardzo ważne dla stabilności i przewidywalności twojego kodu.
Po co to wszystko?
W prawdziwych projektach bardzo często zdarza się, że nie wiesz z góry, ile elementów będziesz miał. Na przykład:
- Lista użytkowników, którzy weszli na stronę.
- Lista produktów w koszyku sklepu internetowego.
- Lista wyników wyszukiwania dla zapytania.
- Lista zadań w planerze, który właśnie będziemy robić.
We wszystkich tych przypadkach liczba elementów może się zmieniać i List<T> staje się naszym niezastąpionym pomocnikiem.
Podstawowe metody List<T>
| Metoda | Opis | Przykład |
|---|---|---|
|
Dodaj element na koniec listy | |
|
Wstaw na podaną pozycję | |
|
Usuń pierwsze wystąpienie elementu | |
|
Usuń element po indeksie | |
|
Wyczyść listę | |
|
Sprawdź, czy element jest na liście | |
|
Indeks pierwszego wystąpienia | |
|
Liczba elementów na liście | |
|
Pojemność wewnętrznego bufora | |
2. Tworzenie listy (List<T>)
Czas na praktykę! Wyobraź sobie, że chcemy zrobić prosty menedżer zadań. Potrzebujemy listy, do której będziemy dodawać nasze sprawy.
Aby stworzyć nową listę List<T> używamy operatora new, dokładnie tak samo jak przy tworzeniu innych obiektów.
Jak zadeklarować i stworzyć List
Składnia tworzenia listy jest bardzo podobna do tablicy, tylko zamiast nawiasów kwadratowych — ostre:
using System.Collections.Generic;
List<int> numbers = new List<int>();
Komentarze dla tych, którzy lubią szczegóły:
- List<int> — to lista liczb całkowitych (int). Zamiast int możesz podstawić dowolny inny typ: string, double, własną klasę itd.
- Po utworzeniu lista jest pusta — jej długość to 0.
Przykład: pierwszy krok w rozwoju naszej aplikacji
Załóżmy, że mamy aplikację, w której użytkownik wpisywał imię i wiek (jeszcze z pierwszych wykładów). Dodajmy teraz możliwość zapisywania kilku imion na liście, żeby potem z nimi pracować.
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<string> names = new List<string>();
Console.WriteLine("Wpisz trzy imiona:");
for (int i = 0; i < 3; i++)
{
string name = Console.ReadLine();
names.Add(name);
}
Console.WriteLine("Wpisałeś:");
foreach (string n in names)
{
Console.WriteLine(n);
}
}
}
Zwróć uwagę na Add(). To właśnie ta metoda dodaje nowe elementy do listy. Dzięki niej lista jest dynamiczna!
Możesz stworzyć listę od razu z elementami!
List<string> planets = new List<string> { "Merkury", "Wenus", "Ziemia" };
Mega wygodne, gdy znasz "skład początkowy". Taka składnia jest często używana w testach i przy inicjalizacji stałych danych.
3. Podstawowe operacje na List<T>
Zanim przejdziemy do trików i magii, ogarnijmy "abecadło" listy.
Dodawanie elementów: Add()
Użycie metody Add() — jak położenie nowej rzeczy na półkę:
List<int> numbers = new List<int>();
numbers.Add(42);
numbers.Add(100);
Teraz w środku: [42, 100]
Pobieranie liczby elementów: Count
W przeciwieństwie do tablicy, lista ma właściwość nie Length, tylko Count:
int count = numbers.Count; // Zwróci 2
Pamiętaj: w listach używamy Count, a w tablicach i stringach — Length. To różne właściwości do podobnego celu.
Dostęp do elementów po indeksie
Dwa sposoby, oba na to samo wychodzą:
int first = numbers[0]; // 42
numbers[1] = 200; // Zamieniliśmy 100 na 200
Ważne: Indeksowanie, jak w tablicach, zaczyna się od zera. Nie próbuj sięgnąć do numbers[3], jeśli masz tylko trzy elementy!
Przeglądanie elementów listy
Możesz użyć pętli for albo foreach:
// Przez for — jeśli potrzebujesz indeksu
for (int i = 0; i < numbers.Count; i++)
{
Console.WriteLine(numbers[i]);
}
// Przez foreach — jeśli indeks niepotrzebny
foreach (int number in numbers)
{
Console.WriteLine(number);
}
foreach zwykle jest prostszy i czytelniejszy, zwłaszcza dla dużych kolekcji.
Ale z nim nie możesz zmieniać elementów po indeksie ani wyjść z pętli wcześniej przez break, jeśli nie używasz dodatkowych konstrukcji.
Wstawianie elementu po indeksie: Insert()
Metoda Insert() przydaje się, gdy chcesz dodać element nie na koniec listy, tylko gdziekolwiek. Na przykład na drugie miejsce:
numbers.Insert(1, 55); // Wstawi 55 na pozycję o indeksie 1
Teraz numbers: [42, 55, 200]
Usuwanie elementów
Usuwanie z listy jest prawie tak proste, jak dodawanie:
numbers.Remove(55); // Usunie PIERWSZE wystąpienie 55
numbers.RemoveAt(0); // Usunie element o indeksie 0
numbers.Clear(); // Całkowicie czyści listę
Niuanse usuwania:
- Jeśli usuniesz nieistniejącą wartość metodą Remove, nic złego się nie stanie: lista się nie zmieni, nie dostaniesz błędu.
- Po Clear() lista znów jest pusta.
4. Przydatne niuanse
Jak rośnie List<T>
Gdy dodajesz elementy do List<T>, nie zwiększa ona rozmiaru za każdym razem po trochu. Zamiast tego List<T> z góry rezerwuje blok pamięci z zapasem (wewnętrzna pojemność — Capacity).
Gdy elementów jest więcej niż aktualna pojemność, lista rezerwuje nowy, większy blok pamięci (zwykle 2x większy) i kopiuje tam stare wartości:
Dodajemy elementy → zmieściło się → zmieściło się → zmieściło się →
Nowy element się nie mieści → tworzony jest większy array → kopiowane dane → lecimy dalej
To pozwala szybciej dodawać elementy, bo pamięć jest przydzielana nie przy każdym Add, tylko raz na jakiś czas.
var list = new List<int>(); // Capacity = 0
list.Add(1); // Capacity = 4
list.Add(2); // Capacity = 4
list.Add(3); // Capacity = 4
list.Add(4); // Capacity = 4
list.Add(5); // Capacity = 8 (rozszerzenie!)
Capacity vs. Count: jaka różnica?
- Count — liczba elementów dodanych do listy.
- Capacity — rozmiar wewnętrznej tablicy przechowującej elementy.
List<int> nums = new List<int>();
Console.WriteLine(nums.Count); // 0
Console.WriteLine(nums.Capacity); // Zwykle 0 albo mały rozmiar
nums.Add(123);
Console.WriteLine(nums.Count); // 1
Console.WriteLine(nums.Capacity); // Teraz może być większe niż Count
nums.Capacity = 1000; // Możesz jawnie zwiększyć Capacity, jeśli wiesz, że będzie dużo elementów
Większość programistów używa tylko Count, ale jeśli piszesz wymagające aplikacje (np. gry albo analizę danych), to precyzyjne ustawienie Capacity może się przydać.
5. Przykład
Rozwijamy naszą małą aplikację — teraz użytkownik może dodawać swoje ulubione liczby, a potem pracować z listą: wstawiać, usuwać, wyświetlać i czyścić.
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<int> favoriteNumbers = new List<int>();
Console.WriteLine("Wpisz swoje ulubione liczby (po jednej w linii, 0 — zakończ):");
while (true)
{
int num = int.Parse(Console.ReadLine());
if (num == 0) break;
favoriteNumbers.Add(num);
}
Console.WriteLine("Twoje ulubione liczby:");
foreach (int n in favoriteNumbers)
{
Console.Write(n + " ");
}
Console.WriteLine("\nChcesz usunąć pierwszą liczbę? (y/n)");
if (Console.ReadLine().ToLower() == "y")
{
favoriteNumbers.RemoveAt(0);
}
Console.WriteLine("A teraz twoja lista:");
foreach (int n in favoriteNumbers)
{
Console.Write(n + " ");
}
}
}
Uwaga: Tutaj używamy int.Parse. W prawdziwych projektach zawsze lepiej sprawdzać poprawność wejścia przez int.TryParse, żeby nie "wywalić" programu na każdej pomyłce użytkownika.
6. Częste błędy i "miny" początkujących
Bardzo popularny scenariusz: piszesz pętlę od 0 do List.Count, ale w trakcie pętli (np. w foreach) zmieniasz samą listę (dodajesz lub usuwasz elementy). Nie rób tak! Programistyczna elita nazywa to "modyfikacją kolekcji podczas iteracji". W większości przypadków od razu dostaniesz wyjątek typu InvalidOperationException.
Przykład złego kodu:
foreach (int n in numbers)
{
if (n < 0)
numbers.Remove(n); // BUM! Runtime error.
}
Poprawnie: albo najpierw zebrać indeksy/elementy do usunięcia w osobnej liście, albo użyć pętli for wstecz.
GO TO FULL VERSION