Poniższy artykuł pochodzi z serii Przygotowań do egzaminu 70-536.
Kolekcje generyczne są bezpieczną (ang. type-safe), szybką i wydajną (ang. performance) wersją ich nie-generycznych odpowiedników. Przykładowo List<>, Dictionary<>, Queue<>, Stack<>, SortedList<> i SortedDictionary<> są odpowiednikami zwykłych kolekcji. Różnica polega na tym, że korzystając z typów generycznych podajemy parameter, który jest zamieniany na nazwę typu podczas kompilacji. W ten sposób można użyć każdego typu, ale tylko wcześniej określonego. W innym wypadku otrzymamy błędy już przy kompilacji. Namiastką tego są kolekcje specjalizowane, np. StringCollection.
Dodatkowo w .NET Framework wprowadzono nowe klasy, które nie mają swoich odpowiedników wśród zwykłych typów.
Generyczna Lista
1: List<int> intList = new List<int>();
2: intList.Add(1);
3: //intList.Add("Eastgroup"); // to się nawet nie skompiluje!
4: intList.Add(2);
5: intList.Add(3);
6: int number = intList[0];
7:
8: foreach (int item in intList)
9: {
10: Console.Write("{0} ", item); // 1 2 3
11: }
W powyższym przykładzie utworzyliśmy listę składającą się ze zmiennych typu int - typów całkowitych. Po jej utworzeniu wykonujemy następujące czynności:
- Dodajemy (metoda Add) elementy do listy. Typ musi zgadzać się z tym z deklaracji - w przeciwnym wypadku pojawią się błędy już na poziomie kompilacji.
- Odwołujemy się do elementów poprzez ich indeksy - w tym przypadku do elementu o indeksie 0.
- Za pomocą pętli foreach iterujemy po elementach listy.
Gdybyśmy chcieli posortować listę w niestandardowy sposób (np. malejąco), możemy wykorzystać metodę Sort i przekazywanego doń delegata. Deklarujemy metodę, która odpowiada typom listy:
1: static int ReverseIntComparison(int x, int y)
2: {
3: return y - x;
4: }
Sortujemy listę w kolejności malejącej:
1: intList.Sort(ReverseIntComparison);
2: foreach (int item in intList)
3: {
4: Console.Write("{0} ", item); // 3 2 1
5: }
Kolejka i Stos
Jak już zostało wspomniane wcześniej są to odpowiedniki zwykłych kolekcji o tej samej nazwie. Tworząc ich instancje określamy, jakiego typu zmienne mogą być w nich przechowywane.
1: Queue<String> que = new Queue<string>();
2: // Dodajemy element typu String na koniec kolejki
3: que.Enqueue("Hello");
4: // Pobieramy pierwszy element z kolejki
5: String queued = que.Dequeue();
W przypadku stosu:
1: Stack<int> serials = new Stack<int>();
2: // Dodajemy element typu int na stos
3: serials.Push(1);
4: // Pobieramy ostatnio dodany element ze stosu
5: int serialNumber = serials.Pop();
Słowniki
Generyczny słownik (ang. Dictionary) odpowiada takim klasom jak Hashtable, ListDictionary, HybridDictionary, OrderedDictionary, NameValueCollection. Słownik przypomina tablice asocjacyjne w innych językach programowania. Wykorzystywany jest do przechowywania par typu klucz/wartość (ang. key/value). Ważną różnicą między generycznym słownikiem a jego odpowiednikim jest to, że wyciągając element za pomocą indeksu, lub iterując po kolekcji, otrzymujemy obiekty typu generycznego KeyValuePair! Typ klucza i wartości zwracanego obiektu będą takie same, jak te wykorzystane w deklaracji słownika.
1: Dictionary<int, string> countyLookup = new Dictionary<int, string>();
2: countyLookup[44] = "United Kingdom";
3: countyLookup[33] = "France";
4: countyLookup[48] = "Poland";
5: countyLookup[55] = "Brazil";
6: //countyLookup["39"] = "Italy"; // Błąd kompilacji!
7:
8: Console.WriteLine("The 48 Code is for: {0}", countyLookup[48]); // The 48 Code is for: Poland
9:
10: foreach (KeyValuePair<int, string> item in countyLookup)
11: {
12: int code = item.Key;
13: string country = item.Value;
14: Console.WriteLine("Code {0} = {1}", code, country);
15: }
W powyższym przykładzie utworzyliśmy słownik zawierający kody krajów i ich nazwy. Pokazaliśmy, jak dostać się do wartości za pomocą klucza, oraz dokonaliśmy iteracji po elementach kolekcji. Dla każdego obiektu KeyValuePair wypisaliśmy jego klucz i wartość.
Uporządkowana lista i słownik
Generyczne SortedList i SortedDictionary porządkują elementy w kolekcji na podstawie klucza.
1: SortedList<string, int> sortList = new SortedList<string, int>();
2: sortList["Jeden"] = 1;
3: sortList["Dwa"] = 2;
4: sortList["Trzy"] = 3;
5:
6: foreach (KeyValuePair<string, int> item in sortList)
7: {
8: Console.Write("{0} ", item); // [Dwa, 2] [Jeden, 1] [Trzy, 3]
9: }
Sposób użycia SortedDictionary jest identyczny.
1: SortedDictionary<string, int> sortedDict = new SortedDictionary<string, int>();
2: sortedDict["Jeden"] = 1;
3: sortedDict["Dwa"] = 2;
4: sortedDict["Trzy"] = 3;
5:
6: foreach (KeyValuePair<string, int> item in sortedDict)
7: {
8: Console.Write("{0} ", item); // [Dwa, 2] [Jeden, 1] [Trzy, 3]
9: }
LinkedList – lista powiązana
Lista powiązana to zbiór elementów, które są ze sobą powiązane. Od jednego elementu możemy przejść do następnego lub poprzedniego.
1: LinkedList<string> links = new LinkedList<string>();
2: LinkedListNode<string> first = links.AddLast("First");
3: LinkedListNode<string> last = links.AddFirst("Last");
4: LinkedListNode<string> second = links.AddBefore(last, "Second");
5: links.AddAfter(second, "Third");
6:
7: foreach (string s in links)
8: {
9: Console.Write("{0} ", s); // Second Third Last First
10: }
Mając listę powiązaną możemy dodawać elementy w następujący sposób:
- Na początku listy (AddFirst),
- Na końcu listy (AddLast),
- Przed pewnym elementem (AddBefore),
- Po pewnym elemencie (AddAfter).
Elementu nie można dodać wskazując jego indeks!
Nasz własny typ generyczny
1: using System.Collections;
2:
3: namespace Generics
4: {
5: public class MyList<T> : ICollection, IEnumerable
6: {
7: private ArrayList _innerList = new ArrayList();
8:
9: public void Add(T val)
10: {
11: _innerList.Add(val);
12: }
13: public T this[int index]
14: {
15: get
16: {
17: return (T)_innerList[index];
18: }
19: }
20:
21:
22: #region ICollection Members
23: // ...
24: #endregion
25:
26: #region IEnumerable Members
27: // ...
28: #endregion
29:
30: }
31: }
Parameter T jest podmieniany nazwą typu podczas kompilacji. Aby korzystać z wyżej podanej kolekcji, należy jeszcze zaimplementować interfejsy ICollection i IEnumerable. Przykład użycia:
1: MyList<double> myDoubleList = new MyList<double>();
2: myDoubleList.Add(13.33337);
3: myDoubleList.Add("Złoooo"); // Błąd na poziomie kompilacji!
Podsumowanie
W niniejszym wpisie skupiłem się głównie na wykorzystaniu klas dostarczonych razem z .NET Framework. Osobom żądnym wiedzy radzę poszukać dodatkowych informacji na temat tworzenia własnych typów, generycznych wyliczeń i porównywań.
Kolejny artykuł z serii to 70-536: Serializing Objects