Kolekcje to wyspecjalizowane klasy pozwalające na przechowywanie i wyszukiwanie danych. Klasy te oferują wsparcie dla: stosów, kolejek, list oraz tablic mieszających. Większość klas kolekcji implementuje te same interfejsy.

Klasy kolekcji służą do realizacji wielu celów, takich jak: dynamiczne przydzielanie pamięci dla elementów i dostęp do tych elementów na bazie indeksów. Klasy te tworzą kolekcje obiektów klasy Object, która jest klasą bazową dla wszystkich typów danych w C#.


Klasy kolekcji oraz ich wykorzystanie

Poniżej lista powszechnie stosowanych kolekcji pochodzących z przestrzeni nazw System.Collection. Każda z nich zostanie omówiona pobieżnie, ponieważ nie wszystkie używane są równie często. Ważne, żebyś miał ogólny ogląd do czego służą poszczególne kolekcje. Przejdziemy od razu na angielskie nazwy, gdyż takich będziemy używać dalej:

  • ArrayList;
  • Hashtable;
  • SortedList;
  • Stack;
  • Queue;
  • BitArray.


ArrayList

ArrayLista reprezentuje uporządkowaną kolekcję obiektu, który może być indeksowany indywidualnie.

Jest to w zasadzie alternatywa dla tablicy (array). Jednakże, w odróżnieniu do tablic, możesz dodać lub usunąć element listy na wskazanej pozycji a nasza ArrayLista zmieni rozmiar automatycznie. Pozwala również na dynamiczne przydzielanie pamięci, dodawanie, przeszukiwanie oraz sortowanie elementów listy.

Tak jak wcześniej wspomniałem pojawią się tutaj skrócone opisy wraz z przykładami dla wymienionych wyżej kolekcji:

using System;
using System.Collections;
namespace ArrayLista
{
    class Program
    {
        static void Main(string[] args)
        {
            ArrayList array = new ArrayList();
            Console.WriteLine("Dodawanie numerów: ");
            array.Add(34);
            array.Add(14);
            array.Add(24);
            array.Add(34);
            array.Add(44);
            // Dynamiczne przydzielanie pamięci
            Console.WriteLine("Liczba elementów, które ArrayLista może zawierać: {0}", array.Capacity);
            Console.WriteLine("Liczba elementów: {0}", array.Count);
            // Dodamy do ArrayListy kolejne liczby i sprawdzimy pojemność
            array.Add(1);
            array.Add(2);
            array.Add(3);
            array.Add(4);
            // Większa pojemność po dodaniu kolejnych zmiennych
            Console.WriteLine("Liczba elementów, które ArrayLista może zawierać: {0}", array.Capacity);
            Console.WriteLine("Liczba elementów: {0}", array.Count);
            // Teraz posortujemy listę
            array.Sort();
            Console.Write("Posortowane liczby: ");
            foreach (var item in array)
            {
                Console.Write(item + " ");
            }
            Console.WriteLine();
            // W artykule pisałem również o automatycznie zmianie rozmiaru tablicy
            // Usuwamy dwa różne elementy i sprawdzamy czy pozostaną puste miejsca
            // czy dane te zostaną autoamtycznie przesunięte "do góry"
            array.RemoveAt(4);
            array.RemoveAt(7);
            Console.WriteLine("Liczba elementów, które ArrayLista może zawierać: {0}", array.Capacity);
            Console.WriteLine("Liczba elementów: {0}", array.Count);
            Console.Write("Posortowane liczby: ");
            foreach (var item in array)
            {
                Console.Write(item + " ");
            }
            Console.WriteLine();
            // Ograniczamy pojemność ArrayListy to liczby elementów wewnątrz
            array.TrimToSize();
            Console.WriteLine("Liczba elementów, które ArrayLista może zawierać: {0}", array.Capacity);
            Console.WriteLine("Liczba elementów: {0}", array.Count);
            array.Add(43);
            Console.WriteLine("Liczba elementów: {0}", array.Count);
            Console.ReadKey();
            // Wynik działania programu
            // Dodawanie numerów:
            // Liczba elementów, które ArrayLista moze zawierac: 8
            // Liczba elementów: 5
            // Liczba elementów, które ArrayLista moze zawierac: 16
            // Liczba elementów: 9
            // Posortowane liczby: 1 2 3 4 14 24 34 34 44
            // Liczba elementów, które ArrayLista moze zawierac: 16
            // Liczba elementów: 7
            // Posortowane liczby: 1 2 3 4 24 34 34
            // Liczba elementów, które ArrayLista moze zawierac: 7
            // Liczba elementów: 7
            // Liczba elementów: 8
        }
    }
}

Hashtable

Wykorzystuje klucz do dostępu do elementu kolekcji.

Kolekcja ta jest używana kiedy chcemy uzyskać dostęp do elementu za pomocą klucza oraz jesteśmy w stanie zidentyfikować użyteczną wartość klucza. Każdy element kolekcji posiada swoją parę klucz/wartość.

using System;
using System.Collections;
namespace HashTable
{
    class Program
    {
        static void Main(string[] args)
        {
            Hashtable ht = new Hashtable();
            // Dodajemy pary: klucz/wartość
            ht.Add("001", "Audi");
            ht.Add("002", "Pagani");
            ht.Add("003", "Lamborghini");
            ht.Add("004", "Nissan");
            ht.Add("005", "Volvo");
            //Dostęp do elementu za pomocą klucza
            if (ht.ContainsKey("005"))
                Console.WriteLine("Wartość klucza: {0}", ht["005"]);
            else
                ht.Add("005", "Volvo");
            // Możemy również sprawdzić czy w kolekcji jest konkretna wartość
            if (ht.ContainsValue("Volvo"))
                Console.WriteLine("Volvo jest elementem kolekcji");
            // Pobierzemy teraz wszystkie klucze
            ICollection key = ht.Keys;
            foreach (string k in key)
            {
                Console.WriteLine("Klucz: {0} || wartość: {1}", k, ht[k]);
            }
            // A na koniec wyczyścimy zawartość
            ht.Clear();
            Console.WriteLine("Pozostała liczba elementów: {0}", ht.Count);
            Console.ReadKey();
            // Wynik działania programu
            // Wartosc klucza: Volvo
            // Volvo jest elementem kolekcji
            // Klucz: 001 || wartosc: Audi
            // Klucz: 002 || wartosc: Pagani
            // Klucz: 005 || wartosc: Volvo
            // Klucz: 004 || wartosc: Nissan
            // Klucz: 003 || wartosc: Lamborghini
            // Pozostala liczba elementów: 0
        }
    }
}

SortedList

Używa klucza lub indeksu żeby uzyskać dostęp do elementu kolekcji.

Lista ta jest kombinacją zwykłej tablicy (array) oraz tablicy mieszającej (Hashtable). Zawiera listę elementów do których dostęp uzyskujemy dzięki zastosowaniu klucza lub indeksu. Jeżeli uzyskujemy dostęp za pomocą indeksu kolekcja ta jest traktowana jak ArrayList, jeżeli używamy klucza kolekcja jest traktowana jak Hashtable. Kolekcja jest zawsze sortowana po wartości klucza.

using System;
using System.Collections;
namespace SortedLists
{
    class Program
    {
        static void Main(string[] args)
        {
            SortedList sl = new SortedList();
            // Dodajemy pary: klucz/wartość
            sl.Add("001", "Audi");
            sl.Add("002", "Pagani");
            // element poniższy zostaje dodany do listy na ostatnią pozycję
            sl.Add("005", "Volvo");
            // element ten zostaje dodany do listy przed pozycją 005
            sl.Add("004", "Nissan"); 
            // element ten zostaje dodany do listy przed powyższym elementem        
            sl.Add("003", "Lamborghini");
            // Dostęp przez klucz -> jak HashTable
            if (sl.ContainsKey("004"))
                Console.WriteLine("Wartość: {0}", sl["004"]);
            else
                sl.Add("004", "Nissan");
            // Dostęp przez wartość -> jak ArrayList
            if (sl.ContainsValue("Nissan"))
                Console.WriteLine("Nissan jest elementem kolekcji");
            ICollection keys = sl.Keys;
            foreach (string k in keys)
            {
                Console.WriteLine("Klucz: {0} || wartość: {1}", k, sl[k]);
            }
            Console.ReadKey();
            // Wynik działania programu
            // Wartosc: Nissan
            // Nissan jest elementem kolekcji
            // Klucz: 001 || wartosc: Audi
            // Klucz: 002 || wartosc: Pagani
            // Klucz: 003 || wartosc: Lamborghini
            // Klucz: 004 || wartosc: Nissan
            // Klucz: 005 || wartosc: Volvo
        }
    }
}

Stack

Stos reprezentuje kolekcję zgodnie z zasadą last-in, first-out. Oznacza to, iż ostatni dodany element znajduje się na pierwszej pozycji na stosie i jest pierwszym elementem do usunięcia.

Jest używana w operacjach, kiedy chcemy aby ostatni przez nas element był na szczycie stosu i był pierwszym elementem do usunięcia. Operacja dodawania nowego elementu do listy to tzw. pushing, operacja usuwania to popping.

using System;
using System.Collections;
namespace Stacks
{
    class Program
    {
        static void Main(string[] args)
        {
            Stack st = new Stack();
            st.Push("A");
            st.Push("B");
            st.Push("C");
            st.Push("D");
            // Nasz stos jest pełny. Zgodnie z definicją
            // kolejny element powinien trafić na pierwszą pozycję
            st.Push("TEST");
            Console.WriteLine("Zawartość stosu: ");
            foreach (var item in st)
            {
                Console.WriteLine(item);
            }
            // Aby spełnić zasadę last-in, first-out sprawdzmy usuwanie
            // kolejnych pozycji
            st.Pop();
            st.Pop();
            Console.WriteLine("Zawartość stosu: ");
            foreach (var item in st)
            {
                Console.WriteLine(item);
            }
            // Rezultat jest zgodny z teorią
            // Możemy również zwrócić obiekt na szczycie stosu bez usuwania
            Console.WriteLine("Pierwszy element: {0}", st.Peek());
            Console.WriteLine("Zawartość stosu: ");
            foreach (var item in st)
            {
                Console.WriteLine(item);
            }
            Console.ReadKey();
            // Wynik działania programu
            // Zawartosc stosu:
            // TEST
            // D
            // C
            // B
            // A
            // Zawartosc stosu:
            // C
            // B
            // A
            // Pierwszy element: C
            // Zawartosc stosu:
            // C
            // B
            // A
        }
    }
}

Queue

Stos reprezentuje kolekcję zgodnie z zasadą first-in, first-out. Oznacza to, iż pierwszy dodany element znajduje się na pierwszej pozycji na stosie i jest pierwszym elementem do usunięcia.

Jest używana w operacjach gdy chcemy aby pierwszy dodany element do kolejki był również pierwszym elementem do usunięcia. Operacja dodawania elementu do kolejki to tzw. enqueue, operacja usunięcia to deque.

using System;
using System.Collections;
namespace Queues
{
    class Program
    {
        static void Main(string[] args)
        {
            Queue q = new Queue();
            q.Enqueue("A");
            q.Enqueue("B");
            q.Enqueue("C");
            q.Enqueue("D");
            // Zgodnie z definicją first-in, first-out
            // Pierwszy dodany element powienien być 
            // pierwszym do usunięcia - sprawdxmy
            q.Dequeue();
            Console.WriteLine("Zawartość kolejki: ");
            foreach (var item in q)
            {
                Console.WriteLine(item);
            }
            // Spróbujmy jeszcze zobaczyć jaki element usuwamy
            string tekst = (string)q.Dequeue();
            Console.WriteLine("Usuneliśmy: {0}", tekst);
            Console.WriteLine("Zawartość kolejki: ");
            foreach (var item in q)
            {
                Console.WriteLine(item);
            }
            Console.ReadKey();
            // Wynik działania programu
            // Zawartosc kolejki:
            // B
            // C
            // D
            // Usunelismy: B
            // Zawartosc kolejki:
            // C
            // D
        }
    }
}

BitArray

Reprezentuje tablicę binarnej reprezentacji z wykorzystaniem wartości 0 oraz 1.

Jest stosowana gdy chcemy przechowywać bity, ale nie znamy z góry liczby bitów, które chcemy przechowywać. Dostęp do elementu kolekcji możemy uzyskać za pomocą indeksu będącego liczbą całkowitą, która zaczyna się od 0.

using System;
using System.Collections;
namespace BitArrays
{
    class Program
    {
        static void Main(string[] args)
        {
            // Tworzymy dwie tablice o rozmiarze 8 bitów
            BitArray ba1 = new BitArray(8);
            BitArray ba2 = new BitArray(8);
            byte[] a = { 60 };
            byte[] b = { 13 };
            // zapisujemy wartości w naszych tablicach
            ba1 = new BitArray(a);
            ba2 = new BitArray(b);
            // Zawartość pierwszej z nich
            Console.WriteLine("Tablica bitów ba: 60");
            for (int i = 0; i < ba1.Count; i++)
            {
                // Co oznacza poniższy zapis
                // Na każdy wyraz poświęcamy 6 znaków
                // Ale z wyrównianiem do lewej strony
                // Gdybyśmy zapis zmienili na {0, 6} 
                // Wyrównanie byłoby do prawej strony
                Console.Write("{0, -6}", ba1[i]);
            }
            Console.WriteLine();
            // Zawartość drugiej z nich
            Console.WriteLine("Tablica bitów ba: 13");
            for (int i = 0; i < ba2.Count; i++)
            {
                Console.Write("{0, -6}", ba2[i]);
            }
            Console.WriteLine();
            // Połączmy teraz obie tablie operatorem logicznym AND
            BitArray ba3 = new BitArray(8);
            ba3 = ba1.And(ba2);
            Console.WriteLine("Tablica bitów ba3: ba1 i ba2");
            for (int i = 0; i < ba3.Count; i++)
            {
                Console.Write("{0, -6}", ba3[i]);
            }
            Console.ReadKey();
            // Wynik działania programu
            // Tablica bitów ba: 60
            // False False True  True  True  True  False False
            // Tablica bitów ba: 13
            // True  False True  True  False False False False
            // Tablica bitów ba3: ba1 i ba2
            // False False True  True  False False False False
        }
    }
}