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
}
}
}