Kolekcje .NET

Jedną z zalet platformy .NET jest bogata biblioteka struktur i klas, gotowa do wykorzystania w projektowanych aplikacjach. Oferuje między innymi zbiór struktur danych nazywanych w kontekście platformy .NET kolekcjami.

Jedną z zalet platformy .NET jest bogata biblioteka struktur i klas, gotowa do wykorzystania w projektowanych aplikacjach. Oferuje między innymi zbiór struktur danych nazywanych w kontekście platformy .NET kolekcjami.

Platforma .NET dostarcza zarówno struktury standardowe - tablice, listy, stosy czy kolejki - jak i wyspecjalizowane w realizacji określonych zadań, m.in. BitArray i SortedList. Klasy te wyposażone są w odpowiednie metody implementujące najbardziej popularne algorytmy związane ze strukturami danych, a więc przede wszystkim sortowania i przeszukiwania. Nie ulega wątpliwości, że najczęściej wykorzystywaną strukturą danych jest tablica, dlatego na jej omówienie poświęcę w tej części kursu najwięcej miejsca. Poza tym przyjrzymy się implementacji listy ArrayList i jednej z kolekcji specjalnych, a mianowicie SortedList.

Tablice

Wielu programistów C i C++ budzi się co rano dręczonych wątpliwościami, czy w pisanym do późnej nocy programie nie przeoczyli pętli, której zakres wykracza poza zakres wykorzystywanej w niej tablicy. Skazane są na to osoby używające typowych tablic korzystających z bezpośredniego odwoływania do komórek pamięci. Krokiem naprzód jest biblioteka STL z szablonem Vector, ale prawdziwym przełomem pod względem wygody i bezpieczeństwa, choć może nie szybkości, jest implementacja tablic przez klasy ze standardowych bibliotek Javy i platformy .NET. W sporym stopniu zmniejszają ryzyko, zachowując jednocześnie wygodę.

Biorąc pod lupę platformę .NET, odkryjemy, że tablica implementowana jest przez klasę System.Array. Zazwyczaj nie trzeba jawnie odwoływać się do tej klasy, bo w C# zdefiniowano aliasy do niej, w dodatku tak, że pozwalają na tworzenie i odwoływanie się do tablic w sposób identyczny, jak do dynamicznie tworzonych tablic w C++ (tj. tworzonych z wykorzystaniem operatora new). Możemy zadeklarować tablicę, czyli utworzyć referencje do niej, zdefiniować ją, czyli zarezerwować dla niej pamięć, a następnie zainicjować, czyli zapełnić jej elementy wartościami, które chcemy przechowywać.

Oto poprawna składnia deklaracji referencji do tablicy z elementami typu int:

int[] i1;

A oto przykład deklaracji referencji wraz z utworzeniem obiektu tablicy (rezerwowana jest pamięć na stercie):

int[] i1 = new int[3];

Po utworzeniu tablica (tj. w istocie instancja klasy System.Array) jest automatycznie inicjowana wartościami domyślnymi dla danego typu, w wypadku obiektów będących instancjami struktur typu int - zerami. Oczywiście możemy także sami zainicjować elementy tablicy. Służy do tego następująca konstrukcja:

int[] i1 = new int[3] {1,2,4};

Inicjowanie tablicy zawierającej typy referencyjne jest konieczne, bo jak wiemy z drugiej części tego kursu, obiekt-instancja klasy nie powstanie, jeśli nie zostanie dla niego wywołany operator new. Przyjrzyjmy się następującym poleceniom:

(1) Button[] b;

MessageBox.Show(""+b[0].Text);

(2) Button[] b = new Button[3];

MessageBox.Show(""+b[0].Text);

(3) Button[] b = new Button[3] {new Button()

new Button(), new Button()};

MessageBox.Show(""+b[0].Text);

Polecenia z grupy (1) w ogóle się nie skompilują, bo w drugim wierszu próbujemy się odwołać do zmiennej b (referencji do tablicy), która nie została zainicjowana. Polecenia (2) skompilują się i program można uruchomić, ale podczas jego działania zgłoszony zostanie wyjątek System.NullReferenceException. Dlaczego? Tablica b jest, co prawda, utworzona (obiekt tablicy powstał) i automatycznie zainicjowana, ale domyślne wartości referencji (a one są w tym wypadku elementami przechowywanymi w tablicy b) to null. Nie powstał, oczywiście, żaden obiekt klasy Button, więc nie mogą one przechowywać żadnych adresów i dlatego próba odczytania własności Text któregokolwiek z elementów tablicy musi skończyć się błędem. Dopiero w poleceniach z grupy (3) tablica zostaje zapełniona referencjami zawierającymi adresy do istniejących obiektów i metoda MessageBox.Show pokaże pustą etykietę przycisku będącego pierwszym elementem tablicy (o indeksie 0).

Obiekty, do których odnoszą się referencje przechowywane w tablicy, nie muszą być tworzone w tym samym wierszu, co deklaracja tablicy. Można do tego równie dobrze wykorzystać pętlę for:

Button[] b = new Button[3];

for (int i=0; i<b.Length; i++)

{

b[i] = new Button();

b[i].Text = ""+i;

MessageBox.Show(b[i].Text);

}

Rysunek 1. Jeżeli tablica instancji klas nie zostanie zainicjowana - jej elementami będą tylko ''puste'' referencje null.

Rysunek 1. Jeżeli tablica instancji klas nie zostanie zainicjowana - jej elementami będą tylko ''puste'' referencje null.

Ważne jest jednak, że niezainicjowanych elementów nie można użyć (nie mogą być argumentem metody poza przypadkiem użycia operatora out i nie mogą być argumentem operatorów oprócz operatora przypisania z lewej strony).

Elementy tablicy indeksowane są od 0, zatem ostatni element ma indeks równy wielkości tablicy minus jeden. Z tego wynika, że w powyższym przykładzie indeksy to 0, 1 i 2. W każdej chwili rozmiar tablicy można odczytać z własności Length. Poważnym ograniczeniem tablic jest to, że po deklaracji nie można zmieniać ich rozmiaru - jeżeli potrzebujemy takiej możliwości, musimy wybrać inną kolekcję, np. listę ArrayList.

Powyższy przykład pokazuje także, że dostęp do elementów tablicy jest identyczny jak w C i C++, tj. za pomocą operatora [] z indeksem umieszczonym wewnątrz.

Nieco inaczej niż w C++ definiuje się natomiast tablicę wielowymiarową. Nie jest to tablica tablic, a nadal jeden obiekt klasy System.Array, którego elementy są odpowiednio poukładane. Oto przykład:

int[,] i2 = new int[2,3] {{0,1,2},{3,4,5}};

Wielkość tej tablicy odczytana za pomocą własności i2.Length równa się 6, ale dostęp do elementów możliwy jest jedynie po umieszczeniu dwóch indeksów wewnątrz nawiasów kwadratowych, np. do ostatniego elementu i2[1,2]. Do zainicjowania tablicy wielowymiarowej można wykorzystać podwójną pętlę for. Oto przykład:

int[,] i2 = new int[2,3];

for(int i=0; i<2; i++)

for(int j=0; j<3; j++)

i2[i,j]=3*i+j;

Pętla foreach

Do modyfikowania zawartości tablicy często wykorzystywane są pętle for o budowie identycznej, jak w powyższych listingach. Jeżeli pętla for przebiega po wszystkich elementach tablicy, czasem wygodniej jest skorzystać z nowego typu pętli, a mianowicie foreach. Oto przykład:

foreach(Button bi in b)

{

bi.Text = ""+0;

MessageBox.Show(bi.Text);

}

Jednak taka pętla nie nadaje się do inicjowania obiektów, bo referencja do elementu (bi w powyższym przykładzie) jest w niej zadeklarowana implicite jako tylko do odczytu. Można zatem zmienić własności obiektów, które wskazuje referencja bi, ale nie można zmienić samej referencji. Gdybyśmy próbowali wykonać następującą pętlę:

foreach(Button bi in b)

{

bi = new Button();

bi.Text = ""+0;

MessageBox.Show(bi.Text);

}

podczas kompilacji zobaczylibyśmy komunikat o błędzie.

Pętla foreach działa także w wypadku tablic wielowymiarowych. Na przykład po wykonaniu poniższych poleceń:

int[,] i2 = new int[2,3] {{0,1,2},{3,4,5}};

foreach(int i in i2) MessageBox.Show(""+i);

zobaczymy liczby od 0 do 5.

Rysunek 2. Warto przejrzeć dokumentację przestrzeni nazw System.Collections, aby zorientować się w kolekcjach oferowanych przez platformę .NET.

Rysunek 2. Warto przejrzeć dokumentację przestrzeni nazw System.Collections, aby zorientować się w kolekcjach oferowanych przez platformę .NET.

W wypadku typu int i innych typów wartościowych fakt, że referencja i jest tylko do odczytu, całkowicie blokuje możliwość modyfikacji elementów tablicy. Następujące polecenie nie zostanie zatem skompilowane:

foreach(int i in i2) i=1;

Metody ze zmienną liczbą argumentów

C# dopuszcza definiowanie metod, w których liczba argumentów nie jest z góry określona. Do tego celu również wykorzystywane są omówione przed chwilą tablice. Przyjrzyjmy się prostemu przykładowi, w którym definiujemy funkcję sumującą liczby typu int podane jako argumenty:

private int Suma(params int[] lista)

{

MessageBox.Show(

"Liczba argumentów: "+lista.Length);

int suma=0;

foreach(int liczba in lista) suma+=liczba;

return suma;

}


Zobacz również