1. Wprowadzenie
No więc wyobraź sobie: masz kolekcję... a musisz szybko dostać się do trzeciego, siódmego albo, powiedzmy, zerowego elementu. Albo zamienić je miejscami. W tablicy to proste — przez indeks (array[3]). A co z kolekcjami? Przecież nie każda kolekcja jest tak samo indeksowalna!
Tutaj na scenę wchodzi interfejs IList<T> — uniwersalny kontrakt, który wymaga od kolekcji obsługi pracy z elementami przez indeks. W skrócie: masz kolekcję, która implementuje IList<T> — możesz śmiało odwoływać się do jej elementów przez indeks (jak w tablicy) i zmieniać je na bieżąco.
Analogicznie:
Pomyśl o karcie bibliotecznej: każda książka ma swój numer na półce i zawsze możesz podejść do "trzeciej z kolei" książki i ją wziąć. Tak właśnie zachowuje się kolekcja wspierająca IList<T>.
2. Ogólna struktura i metody IList<T>
Interfejs IList<T> — bohater wielu kolekcji. Rozszerza ICollection<T> (a ten z kolei IEnumerable<T>, co pozwala przechodzić po kolekcji za pomocą pętli), i dodaje najważniejsze: pracę z indeksem.
Schemat dziedziczenia interfejsów:
IEnumerable<T>
▲
│
ICollection<T>
▲
│
IList<T>
Główne człony interfejsu IList<T>
| Człon | Przeznaczenie |
|---|---|
|
Pobierz lub ustaw element przez indeks |
|
Znajdź indeks pierwszego wystąpienia elementu |
|
Wstaw element na podaną pozycję |
|
Usuń element przez indeks |
Wszystkie pozostałe człony, takie jak Add, Remove, Clear, Contains, pochodzą z interfejsu ICollection<T>.
Kluczowa rzecz: indeksator
Najważniejsza "cecha" IList<T> — obecność indeksatora. To taki syntaktyczny cukier, dzięki któremu możesz pisać:
var myList = new List<int> { 10, 20, 30 };
int secondValue = myList[1]; // Pobieramy 20
myList[2] = 42; // Zmieniamy trzeci element
3. Jakie kolekcje implementują IList<T>
W standardowej bibliotece .NET wiele znanych struktur wspiera interfejs IList<T>. Zobaczmy na najpopularniejsze:
| Kolekcja | Indeksowanie | Opis |
|---|---|---|
|
Tak | Dynamiczna tablica |
|
Tak | Zwykłe tablice są indeksowane |
|
Tak | Używana do data binding |
|
Tak | Lista z powiadomieniami |
|
Tak | Bazowa klasa dla kolekcji |
Uwaga:
LinkedList<T> i
HashSet<T>
NIE implementują
IList<T>, bo nie mają szybkiego indeksowania (tak,
LinkedList<T> nie ma
list[5]!).
4. Przykłady użycia IList<T>
Pobieranie i ustawianie przez indeks
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
IList<string> fruits = new List<string> { "Jabłko", "Banan", "Gruszka" };
// Pobieranie drugiego elementu
string fruit = fruits[1];
Console.WriteLine(fruit); // Banan
// Zamiana trzeciego elementu
fruits[2] = "Pomarańcza";
Console.WriteLine(fruits[2]); // Pomarańcza
}
}
Wstawianie i usuwanie przez indeks
fruits.Insert(1, "Kiwi"); // Wstawiamy "Kiwi" na drugie miejsce
// Lista teraz: "Jabłko", "Kiwi", "Banan", "Pomarańcza"
fruits.RemoveAt(0); // Usuwamy pierwszy element ("Jabłko")
// Lista teraz: "Kiwi", "Banan", "Pomarańcza"
Wyszukiwanie indeksu elementu
int index = fruits.IndexOf("Pomarańcza"); // Zwróci indeks (2) lub -1, jeśli nie znaleziono
if (index != -1)
Console.WriteLine("Pomarańcza znajduje się na pozycji: " + index);
else
Console.WriteLine("Pomarańcza nie znaleziona");
5. Cechy implementacji i typowe błędy
Pracując z IList<T>, łatwo wpaść w kilka "pułapek", szczególnie jeśli zapomnisz, że indeksowanie zaczyna się od zera, a długość kolekcji — to aktualna liczba elementów.
Na przykład, próba odwołania się do nieistniejącego elementu:
Console.WriteLine(fruits[100]); // IndexOutOfRangeException!
Indeksy w C# — to jak kury nioski: zaczynają od zera, nie od jedynki. Jeśli w liście są 4 elementy, ostatni dostępny indeks to 3.
Warto też pamiętać, że nie wszystkie implementacje IList<T> są tak samo szybkie. Na przykład, w tablicy albo w List<T> dostęp przez indeks jest natychmiastowy (O(1)), ale jeśli zrobiłbyś własną kolekcję na bazie listy powiązanej i nałożył na nią IList<T>, operacja mogłaby być wolna. Chociaż standardowa biblioteka tak nie robi.
I jeszcze jedno: jeśli pracujesz z tablicą jako z IList<T>, możesz zmieniać elementy, ale nie możesz zmieniać rozmiaru tablicy. Metody Add, Remove, Insert itd. dla tablicy rzucą NotSupportedException.
int[] myArray = { 1, 2, 3 };
IList<int> listView = myArray; // Upcast
listView[0] = 42; // Działa!
listView.Add(99); // Rzuci NotSupportedException
6. Praktyczne zastosowanie i po co to komu
W prawdziwych projektach prawie co druga kolekcja to coś, co implementuje IList<T>, bo wygodnie mieć możliwość szybkiego dostępu do elementów przez numer, zmieniać je, wstawiać i usuwać przez pozycję. Na przykład:
- Właściwości interfejsowe we ViewModel WPF albo WinForms, gdzie bindują się listy elementów UI.
- Implementacja algorytmów sortowania, wyszukiwania, permutacji, gdzie potrzebny jest dostęp przez indeks.
- Moduły importu/eksportu danych, które pracują z dynamiczną listą obiektów.
Na rozmowie kwalifikacyjnej pytanie o różnice IEnumerable<T>, ICollection<T> i IList<T> — klasyka. Wiedząc, za co odpowiada każdy poziom, możesz pewnie wyjaśnić rekruterowi, czemu HashSet<T> nie implementuje IList<T> (bo unikalność jest ważniejsza niż kolejność i indeksy!).
GO TO FULL VERSION