Menu

Lista w C#: Add, Remove, Contains, Find i Sort

List<T> to rozszerzalna tablica w C#. Zobacz, jak utworzyć listę, dodawać i wstawiać elementy, usuwać według wartości, indeksu lub warunku, wyszukiwać przez Contains i Find, sortować według właściwości i uniknąć błędu przy zmienianiu listy wewnątrz foreach.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

List<T> to uporządkowana kolekcja, która rośnie i maleje w miarę dodawania i usuwania elementów. Znajduje się w System.Collections.Generic, indeksuje się ją jak tablicę (list[0]) i jest domyślnym wyborem w C#, gdy potrzebujesz "zbioru rzeczy".

Tworzenie listy i dodawanie elementów

Parametr typu w nawiasach ostrych mówi, co przechowuje lista: List<string>, List<int>, List<Order>.

Wynik:

2
Zoe, Ana, Ben, Chloe, Dev
Ana
Ana B.
3

Count to właściwość, a nie metoda (tablice używają Length, LINQ ma metodę Count(); na liście używaj właściwości). Insert przesuwa każdy dalszy element o jedno miejsce, więc wstawianie na początek dużej listy zajmuje czas proporcjonalny do jej rozmiaru. Add na końcu to tania operacja.

Usuwanie elementów

Są cztery sposoby usuwania, zależnie od tego, co wiesz o elemencie:

Wynik:

True: 85, 62, 40, 91, 55
False
62, 40, 91, 55
2 removed: 62, 91
Caught ArgumentOutOfRangeException
0

Remove przyjmuje wartość i usuwa tylko pierwsze dopasowanie. Aby usunąć wszystkie kopie wartości, użyj RemoveAll(s => s == 40). Zły indeks na liście rzuca ArgumentOutOfRangeException (tablica rzuca zamiast tego IndexOutOfRangeException), zarówno dla list[i], jak i dla RemoveAt(i). RemoveRange(index, count) usuwa cały blok.

Count a Capacity

Lista trzyma elementy w wewnętrznej tablicy. Capacity to rozmiar tej tablicy; Count to liczba zajętych miejsc. Gdy Count osiągnie Capacity, następne Add alokuje tablicę dwa razy większą i kopiuje do niej wszystko.

Wynik:

Count 0, Capacity 0
Count 1, Capacity 4
Count 2, Capacity 4
Count 3, Capacity 4
Count 4, Capacity 4
Count 5, Capacity 8
Count 6, Capacity 8
Count 7, Capacity 8
Count 8, Capacity 8
Count 9, Capacity 16
Count 0, Capacity 1000

Podwajanie sprawia, że większość wywołań Add to tylko zapis do wolnego miejsca. Jeśli wiesz mniej więcej, ile elementów przybędzie, przekaż tę liczbę do konstruktora, aby pominąć pośrednie kopiowania. Pamiętaj, że new List<int>(1000) nadal ma Count równe 0: list[0] na takiej liście rzuca wyjątek. Pojemność to miejsce, a nie elementy.

Wyszukiwanie: Contains, IndexOf, Find, Exists

Wynik:

True
2
-1
32.00
2 cheap items
False
2
True
False

Find zwraca pierwsze dopasowanie albo wartość domyślną typu (null dla klasy), gdy nic nie pasuje, więc sprawdź wynik przed użyciem. Ostatnia linia wypisuje False, bo Contains używa Equals, a klasa bez nadpisanego Equals porównuje referencje: nowy Product z tymi samymi polami to inny obiekt. Zamiast tego szukaj według właściwości przez Exists albo Find albo nadpisz w klasie Equals i GetHashCode.

Każda z tych metod przechodzi listę od początku, więc każde wywołanie to O(n). Przy wielokrotnym wyszukiwaniu według klucza Dictionary odpowiada w stałym czasie.

Sortowanie listy

Sort() sortuje w miejscu według naturalnego porządku elementów. Przekaż lambdę porównującą, aby sortować według czegokolwiek innego.

Wynik:

1, 2, 5, 9
9, 5, 2, 1
Ben 95, Ana 120, Chloe 150
Chloe 150, Ben 95, Ana 120
Ben 95, Ana 120, Chloe 150

Porównanie zwraca liczbę ujemną, gdy a powinno być pierwsze, dodatnią, gdy b, i zero przy remisie; dokładnie to daje CompareTo. W praktyce liczą się dwie różnice między Sort a OrderBy z LINQ:

  • Sort zmienia listę; OrderBy zwraca nową sekwencję i zostawia listę w spokoju.
  • Sort nie jest stabilne: elementy równe w porównaniu mogą zamienić się miejscami. OrderBy jest stabilne, a ThenBy dodaje drugi klucz: players.OrderBy(p => p.Score).ThenBy(p => p.Name).

Wywołanie Sort() bez porównania na liście obiektów twojej własnej klasy rzuca InvalidOperationException, bo lista nie wie, jak uporządkować elementy. Przekaż porównanie albo zaimplementuj w klasie IComparable<T>.

Reverse() odwraca kolejność w miejscu. Ponieważ System.Linq też definiuje rozszerzenie Reverse, list.Reverse() na List<T> wybiera wersję działającą w miejscu, która nic nie zwraca.

Pętle i usuwanie w trakcie iteracji

foreach odczytuje każdy element po kolei. Pętla for daje też indeks. Nie można natomiast dodawać ani usuwać elementów w trakcie foreach po tej samej liście:

Wynik:

Caught InvalidOperationException
120, 80, 60
120, 80, 60

Wyjątek to InvalidOperationException z komunikatem "Collection was modified; enumeration operation may not execute." Pętla for idąca do przodu nie rzuca wyjątku, ale po cichu pomija element tuż za każdym usuniętym, bo wszystko przesuwa się o jeden indeks w dół. Pętla od tyłu tego unika. Trzecie rozwiązanie to iterowanie po kopii: foreach (var t in orders.ToList()).

Zmiana właściwości elementu wewnątrz foreach (na przykład order.Status = "sent") jest w porządku. Dodawanie lub usuwanie elementów psuje enumerator, podobnie jak zastąpienie elementu przez indekser (orders[i] = 0).

Konwersja między listami a tablicami

Wynik:

4
3
4, 4, 4, 4
Oslo / Lima / Pune / Kyiv

W obu kierunkach elementy są kopiowane do nowego miejsca w pamięci, więc późniejsza zmiana listy nie wpływa na tablicę. ConvertAll to własna wersja listy odpowiadająca Select(...).ToList() z LINQ. Wypisanie listy przez Console.WriteLine(list) pokazuje nazwę typu (System.Collections.Generic.List`1[System.String]); użyj string.Join.

Ściągawka

ZadanieKod
Utworzenievar l = new List<int>(); albo new List<int> { 1, 2 }
Dodanie na końcul.Add(x), l.AddRange(items)
Wstawienie na pozycjęl.Insert(i, x)
Usunięcie pierwszego dopasowanial.Remove(x) (zwraca bool)
Usunięcie według indeksul.RemoveAt(i)
Usunięcie według warunkul.RemoveAll(x => ...) (zwraca liczbę)
Liczba elementówl.Count
Czy zawieral.Contains(x), l.Exists(x => ...)
Wyszukaniel.Find(...), l.FindAll(...), l.FindIndex(...)
Sortowanie w miejscul.Sort(), l.Sort((a, b) => ...)
Posortowana kopial.OrderBy(x => ...).ToList()
Do tablicyl.ToArray()

Typowe błędy

  • Usuwanie wewnątrz foreach. Rzuca InvalidOperationException; użyj RemoveAll albo pętli for od tyłu.
  • Oczekiwanie, że Remove(x) usunie każdą kopię. Usuwa pierwszą; użyj RemoveAll.
  • Używanie Contains na obiektach bez Equals. Porównuje referencje; szukaj według właściwości przez Exists.
  • Indeksowanie listy utworzonej z pojemnością. new List<int>(10) jest pusta; najpierw dodaj elementy.
  • Założenie, że Sort zachowuje kolejność remisów. Nie zachowuje; użyj OrderBy, gdy kolejność równych elementów ma znaczenie.
  • Przypisanie listy, żeby ją współdzielić, a potem zaskoczenie zmianami. var copy = list; to ta sama lista; new List<T>(list) to kopia.

Najczęściej zadawane pytania

Jak dodać elementy do List w C#?

list.Add(item) dopisuje jeden element na końcu, list.AddRange(otherCollection) dopisuje wiele, a list.Insert(index, item) wstawia element na pozycję i przesuwa resztę dalej. Listę możesz też wypełnić przy tworzeniu: var names = new List<string> { "Ana", "Ben" };.

Jak usunąć element z List w C#?

Remove(value) usuwa pierwszy pasujący element i zwraca true, jeśli go znalazł. RemoveAt(index) usuwa według pozycji. RemoveAll(x => condition) usuwa każdy pasujący element i zwraca, ile ich usunął. Clear() opróżnia listę.

Jak sprawdzić, czy List zawiera wartość w C#?

list.Contains(value) zwraca true albo false. Porównuje przez Equals, więc dla twoich własnych klas porównuje referencje, chyba że klasa nadpisuje Equals. Aby sprawdzić według właściwości, użyj list.Exists(p => p.Name == "Ana") albo list.Any(...) z LINQ.

Jak posortować List według właściwości w C#?

Przekaż porównanie do Sort: people.Sort((a, b) => a.Age.CompareTo(b.Age)) sortuje w miejscu. Zamień a i b, aby sortować malejąco. Możesz też użyć LINQ, który zwraca nową posortowaną sekwencję i zachowuje pierwotną kolejność równych elementów: people.OrderBy(p => p.Age).ToList().

Dlaczego dostaję "Collection was modified; enumeration operation may not execute"?

Elementy zostały dodane, usunięte albo zastąpione (list[i] = x) w czasie, gdy foreach przechodziła po tej samej liście, a enumerator w następnym kroku rzuca InvalidOperationException. Użyj list.RemoveAll(condition), pętli for od tyłu albo iteruj po kopii, na przykład list.ToList().

Czym różni się Count od Capacity?

Count to liczba elementów na liście. Capacity to rozmiar wewnętrznej tablicy, który wynosi co najmniej Count. Gdy tablica się zapełni, lista alokuje nową, dwa razy większą, i kopiuje elementy, więc Add jest średnio szybkie.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ