List w C# - tworzenie, operacje i wybór kolekcji

Lista zadań w Visual Studio, zawierająca wpisy takie jak "TODO" i "HACK", z odniesieniami do plików C# i numerów linii.

Napisano przez

Jacek Zając

Opublikowano

15 wrz 2026

Spis treści

Gdy aplikacja ma przechowywać zmienną liczbę elementów, najczęściej sięgam po List. W tym artykule pokazuję, jak tworzyć listy w C#, dodawać, usuwać i wyszukiwać dane, a także kiedy lepiej wybrać tablicę, HashSet albo słownik.

Lista w C# sprawdza się wtedy, gdy dane mają kolejność i mogą się zmieniać

  • List przechowuje elementy jednego typu i pozwala dynamicznie zmieniać ich liczbę.
  • Elementy są indeksowane od 0, zachowują kolejność dodania i mogą się powtarzać.
  • Add, Remove, Contains i Sort rozwiązują większość codziennych zadań.
  • Właściwość Capacity pomaga ograniczyć koszt wielokrotnego powiększania listy.
  • Do szybkiego wyszukiwania po kluczu lepszy będzie Dictionary.
dynamic array index diagram

Czym jest List i kiedy warto go wybrać

List to generyczna kolekcja oparta na dynamicznie powiększanej tablicy. Litera T oznacza typ elementów, więc lista liczb, napisów i obiektów wygląda podobnie, ale zachowuje kontrolę typów już na etapie kompilacji.

W przeciwieństwie do zwykłej tablicy lista nie wymaga znajomości końcowego rozmiaru. Możesz zacząć od pustej kolekcji, a później dodawać rekordy pobrane z formularza, API albo bazy danych. Z mojego doświadczenia wynika, że właśnie ta elastyczność sprawia, że początkujący programiści używają jej bardzo często.

using System.Collections.Generic;

List technologie = new List
{
    "C#",
    "ASP.NET Core",
    "SQL"
};

Lista zachowuje kolejność elementów, pozwala na duplikaty i udostępnia indeksowanie od zera. Oznacza to, że pierwszy element odczytasz przez technologie[0], a liczbę elementów sprawdzisz za pomocą właściwości Count.

Jak tworzyć listy i dodawać do nich dane

Najbardziej klasyczna składnia korzysta z konstruktora new List(). W nowszym C# możesz użyć krótszego zapisu z operatorami wnioskowania typu, ale na początku warto znać obie formy, ponieważ spotkasz je w różnych projektach.

List liczby = new();
liczby.Add(10);
liczby.Add(20);
liczby.Add(30);

liczby.AddRange(new[] { 40, 50 });

Console.WriteLine(liczby.Count);

Metoda Add dopisuje jeden element na końcu, a AddRange pozwala dodać całą kolekcję. Przy większych porcjach danych wybieram drugą opcję, bo kod jest czytelniejszy i często ogranicza liczbę operacji związanych z powiększaniem wewnętrznej tablicy.

W C# 12 dostępne są również wyrażenia kolekcji, dzięki którym listę można zapisać bardzo zwięźle.

List punkty = [10, 20, 30, 40];

Nie każda lista musi być tworzona bez określenia rozmiaru. Jeżeli wiem, że wczytam około 1000 elementów, mogę przekazać przewidywaną pojemność już w konstruktorze.

List logi = new List(1000);

To nie blokuje dodania większej liczby elementów. Ustawia jedynie początkową wartość Capacity, czyli miejsce zarezerwowane wewnątrz kolekcji.

Najważniejsze operacje na liście

Codzienna praca z listą zwykle sprowadza się do kilku metod. Najważniejsze jest rozróżnienie między Count, które mówi, ile elementów faktycznie znajduje się w kolekcji, a Capacity, która opisuje zarezerwowane miejsce.

Operacja Przykład Zastosowanie
Dodanie elementu lista.Add(element) Dopisanie wartości na końcu
Dodanie wielu elementów lista.AddRange(dane) Połączenie danych z inną kolekcją
Odczyt po indeksie lista[2] Szybki dostęp do konkretnej pozycji
Wyszukiwanie lista.Contains(element) Sprawdzenie, czy wartość istnieje
Usunięcie wartości lista.Remove(element) Usunięcie pierwszego pasującego elementu
Usunięcie po indeksie lista.RemoveAt(2) Usunięcie elementu z konkretnej pozycji
Filtrowanie lista.RemoveAll(x => x < 0) Usunięcie elementów spełniających warunek
Sortowanie lista.Sort() Ułożenie elementów rosnąco

Przy odczycie indeksu trzeba uważać na zakres. Próba użycia indeksu mniejszego od zera albo większego lub równego Count zakończy się wyjątkiem ArgumentOutOfRangeException. Jeżeli nie masz pewności, czy element istnieje, najpierw sprawdź liczbę elementów albo użyj bezpiecznego wyszukiwania.

if (technologie.Count > 0)
{
    Console.WriteLine(technologie[0]);
}

Usuwanie elementów bez typowych błędów

Jedna z częstszych pułapek pojawia się wtedy, gdy ktoś usuwa elementy wewnątrz pętli foreach. Lista zmienia wtedy swoją strukturę w trakcie iterowania, co może doprowadzić do wyjątku albo pominięcia części danych.

List liczby = [1, 2, 3, 4, 5, 6];

liczby.RemoveAll(x => x % 2 == 0);

RemoveAll jest w takim przypadku najczytelniejszym rozwiązaniem. Gdy potrzebujesz bardziej złożonego warunku, możesz przejść po indeksach od końca do początku. Usunięcie elementu nie zmieni wtedy pozycji tych, które dopiero mają zostać sprawdzone.

for (int i = liczby.Count - 1; i >= 0; i--)
{
    if (liczby[i] < 0)
    {
        liczby.RemoveAt(i);
    }
}

Metoda Remove usuwa tylko pierwsze wystąpienie wskazanej wartości. Jeśli lista zawiera kilka takich samych elementów, użyj RemoveAll, gdy chcesz usunąć je wszystkie.

Wydajność, Capacity i sposób przechowywania danych

Lista zapewnia szybki dostęp po indeksie, ponieważ elementy znajdują się w wewnętrznej tablicy. Odczyt wartości przez indeks jest operacją stałoczasową, natomiast wyszukiwanie przez Contains sprawdza elementy po kolei i przy dużych kolekcjach może być wyraźnie wolniejsze.

Operacja Typowy koszt
Odczyt po indeksie O(1)
Dodanie na końcu Amortyzacyjnie O(1)
Wstawienie w środku O(n)
Usunięcie ze środka O(n)
Contains lub IndexOf O(n)
Sortowanie Zwykle O(n log n)

Gdy zabraknie miejsca, lista tworzy większą tablicę i kopiuje do niej dotychczasowe elementy. Dokumentacja Microsoftu wskazuje, że podanie przewidywanej pojemności ogranicza koszt wielokrotnych realokacji. Nie ma sensu ustawiać ogromnej wartości na zapas, ale przy znanym rozmiarze danych jest to prosta optymalizacja.

Po zakończeniu budowania listy możesz sprawdzić jej parametry.

Console.WriteLine($"Elementy: {lista.Count}");
Console.WriteLine($"Pojemność: {lista.Capacity}");

Nie myl jednak optymalizacji z przedwczesnym komplikowaniem kodu. Dla kilkudziesięciu lub kilkuset elementów różnica zwykle będzie pomijalna. Capacity zaczyna mieć praktyczne znaczenie przy dużych kolekcjach, częstym dodawaniu danych albo kodzie wykonywanym tysiące razy.

List a tablica i inne kolekcje

Najlepsza kolekcja zależy od operacji, które wykonujesz najczęściej. Sam rozmiar danych nie wystarczy do podjęcia dobrej decyzji, bo lista, zbiór i słownik rozwiązują różne problemy.

Kolekcja Wybierz ją, gdy Ograniczenie
List Potrzebujesz kolejności, indeksów i dynamicznego rozmiaru Wyszukiwanie wartości jest liniowe
T[] Rozmiar jest stały i zależy Ci na prostocie Nie można łatwo zmieniać długości
HashSet Chcesz przechowywać unikalne wartości i szybko sprawdzać ich obecność Nie służy do pracy z indeksami
Dictionary Szukasz wartości po kluczu Każdy klucz musi być unikalny
LinkedList Często wstawiasz lub usuwasz elementy w znanych węzłach Dostęp po indeksie jest niepraktyczny

Jeżeli tworzysz listę użytkowników i chcesz wyświetlić ich w ustalonej kolejności, List będzie naturalnym wyborem. Jeśli natomiast pytasz głównie „jaki użytkownik ma ten identyfikator?”, lepiej sprawdzi się słownik. To rozróżnienie oszczędza sporo niepotrzebnego kodu i późniejszych optymalizacji.

Praktyczny przykład listy obiektów

W prawdziwych aplikacjach lista rzadko przechowuje same liczby. Znacznie częściej zawiera obiekty, na przykład produkty, zadania albo użytkowników.

public class Zadanie
{
    public string Tytul { get; set; } = "";
    public bool Ukonczone { get; set; }
}

List zadania =
[
    new Zadanie { Tytul = "Przygotować formularz", Ukonczone = false },
    new Zadanie { Tytul = "Napisać testy", Ukonczone = true }
];

foreach (Zadanie zadanie in zadania)
{
    Console.WriteLine($"{zadanie.Tytul}: {zadanie.Ukonczone}");
}

Taki model jest prosty, ale dobrze pokazuje najważniejszą zaletę generyków. Lista wie, że przechowuje obiekty typu Zadanie, więc edytor może podpowiadać właściwości, a kompilator wykryje pomyłki wcześniej.

Do filtrowania obiektów często wykorzystuję LINQ. Poniższy kod tworzy nową kolekcję tylko z aktywnymi zadaniami, nie zmieniając pierwotnej listy.

List otwarte =
    zadania.Where(z => !z.Ukonczone).ToList();

W tym przykładzie ToList() materializuje wynik, czyli faktycznie tworzy nową listę. To ważne, gdy wynik ma być używany później albo przekazany do innej części aplikacji.

Jak podejmować dobrą decyzję przy wyborze kolekcji

Jeżeli potrzebujesz kolejności, duplikatów i dostępu po indeksie, zacznij od List. Jeżeli najważniejsza jest unikalność, wybierz HashSet, a gdy podstawową operacją będzie wyszukiwanie po identyfikatorze, rozważ Dictionary.

Najczęstszy błąd nie polega na tym, że ktoś wybiera złą kolekcję na początku. Problem pojawia się wtedy, gdy później ignoruje sposób użycia danych. Przejrzenie kilku operacji w kodzie i sprawdzenie, czy dominują odczyty po indeksie, wyszukiwanie, czy usuwanie ze środka, zwykle szybko pokazuje właściwy kierunek.

Dobrze dobrana lista nie wymaga skomplikowanych sztuczek. W większości aplikacji wystarczy pilnować indeksów, nie modyfikować kolekcji podczas bezpośredniej iteracji i świadomie rozróżniać Count od Capacity. To właśnie te trzy nawyki robią największą różnicę w codziennej pracy z kolekcjami C#.

FAQ - Najczęstsze pytania

List<T> sprawdzi się, gdy potrzebujesz kolejności, indeksów, duplikatów i dynamicznego rozmiaru. Tablica jest lepsza przy stałej długości, HashSet<T> przy unikalnych wartościach, a Dictionary<TKey, TValue> przy wyszukiwaniu danych po unikalnym kluczu.

Najczytelniej użyć metody RemoveAll, na przykład lista.RemoveAll(x => x < 0). Przy bardziej złożonym warunku można iterować po indeksach od końca do początku i stosować RemoveAt, aby usuwanie nie zmieniło pozycji elementów oczekujących na sprawdzenie.

Count określa liczbę elementów faktycznie znajdujących się na liście, a Capacity opisuje zarezerwowane miejsce w wewnętrznej tablicy. Jeśli przewidujesz około 1000 elementów, możesz użyć new List<string>(1000), aby ograniczyć koszt wielokrotnego powiększania kolekcji.

Odczyt po indeksie ma koszt O(1), a dodawanie na końcu jest amortyzacyjnie O(1). Wstawianie i usuwanie ze środka oraz wyszukiwanie przez Contains lub IndexOf mają koszt O(n), natomiast sortowanie zwykle O(n log n).

Oceń artykuł

Ocena: 0.00 Liczba głosów: 0

Tagi:

c# kolekcje tablice linq słowniki

Udostępnij artykuł

Jacek Zając

Jacek Zając

Nazywam się Jacek Zając i od sześciu lat zajmuję się programowaniem webowym. Moja przygoda z tym obszarem zaczęła się od chęci stworzenia własnej strony internetowej, co szybko przerodziło się w pasję do tworzenia aplikacji i rozwiązań, które mogą ułatwiać życie innym. Lubię dzielić się wiedzą, szczególnie w zakresie podstaw programowania oraz budowania responsywnych aplikacji internetowych. W moich tekstach staram się w przystępny sposób wyjaśniać złożone zagadnienia, porównując różne podejścia i technologie, a także zwracając uwagę na aktualne trendy w branży. Zawsze dbam o to, aby informacje, które przekazuję, były rzetelne i zrozumiałe, a także aby były zgodne z najnowszymi standardami. Moim celem jest nie tylko przekazywanie wiedzy, ale także inspirowanie innych do rozwijania swoich umiejętności w programowaniu.

Napisz komentarz