Artykuł

sxc.hu sxc.hu
maj 02 2010
0

Operacje na kolekcjach w C#

Wielu programistów, zwłaszcza tych początkujących, przez cały czas kurczowo trzyma się tablic, jako uniwersalnego sposobu na przechowywanie dużej ilości określonych zmiennych/obiektów. I może jest to jakaś opcja, ale sumarycznie tablice dają duże ograniczenia. Deklarując tablicę, trzeba znać z góry ustaloną ilość elementów jaka będzie miała się znaleźć w tej tablicy i tym samym alokując dużą ilość pamięci już na starcie (być może taką ilość, której nawet nie wykorzystamy), ponadto tablice nie mają wielu przydatnych metod, np. do wyszukiwania elementów i generalnie są po prostu mało elastyczne. Warto wiedzieć, że w przypadku większości nowoczesnych języków programowania, istnieje alternatywa. Tą alternatywą są kolekcje, które rozwiązują przytoczone wyżej problemy. W dzisiejszej notce, zaprezentuje trzy rodzaje kolekcji dostępne w języku C# i przedstawię garść metod z jakich można korzystać w ich przypadku (w większości bazując na liście).

Klasa Person - obszar dla naszych manewrów

Ponieważ nasze kolekcje muszą coś przechowywać (a typy proste są nudne:P), stworzymy bardzo prosta klasę Person. Przechowywać ona będzie następujące dane:

  • Id osoby
  • Imię
  • Nazwisko
  • Wiek

Dostęp do pól odbywać się będzie przez propertisy.

namespace Collections
{
    public class Person
    {
        public int PersonId { get; private set; }
        public string FirstName { get; private set; }
        public string LastName { get; private set; }
        public int Age { get; private set; }

        public Person(int nPersonId, string sFirstName, string sLastName, int nAge)
        {
            PersonId = nPersonId;
            FirstName = sFirstName;
            LastName = sLastName;
            Age = nAge;
        }
    }
}

Klasa List

Lista jest pierwszą z kolekcji, która chciałem dziś przedstawić. W tym wpisie wykorzystamy tylko kolejki typowane, które znajdują się w zestawie System.Collections.Generic. Deklaracja takiej klasy prezentuje się następująco:

List<T>

W nawiasach ostrych podajemy typ obiektu/zmiennej czyli np. string czy utworzoną przez nas klasę Person:

List<Person>oPersonsList = new List<Person>();

I mamy już piękną listę obiektów:). Każdy nowy obiekt dodawany jest na koniec listy. Aby dodać nowy obiekt, musimy skorzystać z metody Add, której przekazujemy obiekt klasy Person:

oPersonsList.Add(new Person(1, "Jan", "Kowalski", 24));

Jeśli chcielibyśmy się teraz dowiedzieć, ile obiektów klasy Person tkwi na naszej liście, możemy skorzystać z właściwości Count:

Console.WriteLine(oPersonsList.Count);

Spróbujmy teraz wyświetlić teraz osoby z listy. Warto byłoby jednak dodać przedtem kilka dodatkowych osób. Wyświetlać elementy możemy przy użyciu pętli foreach oraz for:

oPersonsList.Add(new Person(1, "Jan", "Kowalski", 24));
oPersonsList.Add(new Person(2, "Jan", "Nowak", 26));
oPersonsList.Add(new Person(3, "Anna", "Gruszka", 28));

foreach (Person oPerson in oPersonsList)
{
    Console.WriteLine("Id osoby: " + oPerson.PersonId +
        "\nImię: " + oPerson.FirstName +
        "\nNazwisko: " + oPerson.LastName +
        "\nWiek: " + oPerson.Age + " lat.");
}

int nPersonsNo = oPersonsList.Count;
for (int nCounter = 0; nCounter < nPersonsNo; ++nCounter)
{
    Person oPerson = oPersonsList[nCounter];
    Console.WriteLine("Id osoby: " + oPerson.PersonId +
        "\nImię: " + oPerson.FirstName +
        "\nNazwisko: " + oPerson.LastName +
        "\nWiek: " + oPerson.Age + " lat.");
}

Oba przedstawione wyżej sposoby dadzą taki sam rezultat. Zalecany (i prostszy) jest sposób pierwszy z instrukcją Foreach. Druga konstrukcja przypomina trochę wyświetlanie zawartość tablic (do dla tych, którym ciężko się z nimi rozstać;)).

Aby wyczyścić listę, stosujemy metodę Clear, ale może na razie jednak warto się z tym wstrzymać;)

oPersonsList.Clear();

Teraz, spróbujemy wyszukać na liście osobę, która ma 26 lat. Wykorzystamy do tego metodę Find:

Person oFoundPerson = oPersonsList.Find(oElement => oElement.Age == 26);

Prześledźmy teraz powyższy kod. Skorzystaliśmy tutaj z predykatów. Najpierw wyszczególniamy obiekt kolekcji (oElement), który będzie służył do porównywania wartości, a następnie dokonujemy porównania właściwości wieku z liczbą 26. Jeśli to porównanie zadziała w przypadku jakiegokolwiek obiektu to referencja znalezionego obiektu zostanie umieszczona w oFoundPerson. W przeciwnym przypadku, pozostanie on nullem.

Powyższy kod znajduje tylko jedną osobę i potem zatrzymuje swoje działanie. Co jednak, jeśli takich osób jest więcej, a my byśmy chcieli je wszystkie? Otóż w takim przypadku, istnieje metoda FindAll, która zwraca... listę. Ale nie byle jaką listę bo listę wyfiltrowanych obiektów. Załóżmy teraz, że będziemy szukać wszystkich Janów, oto co zrobimy:

List<Person> oFoundPersonsList = oPersonsList.FindAll(oElement => oElement.FirstName.Equals("Jan"));

Było już wyszukiwanie jednej osoby, a nawet wielu osób. Kolekcje dają również możliwość sprawdzenia, czy określony obiekt istnieje na liście. Sprawdźmy czy na liście naszych ludków, jest jakaś Anna:

bool bExist = oPersonsList.Exists(oElement => oElement.FirstName.Equals("Anna"));

Porządek w kolekcji, możemy też odwrócić, a służy do tego metoda Reverse:

oPersonsList.Reverse();

Standardowa metoda, do dodawania elementów - Add, dodaje element na końcu listy. Możemy jednak wstrzyknąć element w dowolne miejsce listy, wystarczy że skorzystamy z metody Insert:

oPersonsList.Insert(2, new Person(777, "Kasia", "Kowalczyk", 22));

Przepis na sukces w tym przypadku jest prosty. Podajemy numer indeksu, gdzie ma nastąpić dodanie oraz referencję do obiektu.

Klasa Dictionary

Klasa Dictionary jest kolejną z klas kolekcji. Pozwala ona na dodawanie do listy elementów o określonym kluczu (ID). Wielu osobom, może się to kojarzyć z tablicami asocjacyjnymi. Spójrzmy na deklarację takiej klasy:

Dictionary<T,T>

Pierwszy parametr T oznacza typ klucza, drugi oznacza typ obiektu przypisanego do naszego klucza. Czyli chcąc utworzyć Dictionary z użyciem klasy Person możemy zrobić np. tak:

Dictionary<int, Person> oPersonsDictionary = new Dictionary<int,Person>();

Utworzyliśmy nowy słownik, w którym kluczem będzie wartość int czyli nasz identyfikator użytkownika, a wartością obiekt klasy Person. Tak naprawdę, przy takiej implementacji moglibyśmy wyrzucić PersonId z klasy Person, ponieważ dochodzi tutaj mały dubel danych, ale starczy już mieszania;). Nowe osoby dodajemy oczywiście metodą Add. To jest właśnie ta magiczna cecha. Dziedziczą z określonych interfejsów, dzięki czemu wszędzie korzystamy z tych samych metod (oczywiście odpowiednio zaimplementowanych na potrzeby danej kolekcji):

oPersonsDictionary.Add(1, new Person(1, "Jan", "Kowalski", 24));
oPersonsDictionary.Add(2, new Person(2, "Jan", "Nowak", 26));
oPersonsDictionary.Add(3, new Person(3, "Anna", "Gruszka", 28));

Klasa Dictionary, daje nam swobodę w pobieraniu elementów. Wystarczy znać tylko wartość klucza:

if(oPersonsDictionary.ContainsKey(1))
{
    Person oPerson = oPersonsDictionary[1];
}

Metoda ContainsKey, to takie zabezpieczenie na wypadek, gdyby okazało się, że klucz nie istnieje w obiekcie klasy Dictionary. Mogło by to doprowadzić do wyjątku.

Oczywiście również w tym przypadku, mam dostęp do metody Count:

Console.WriteLine(oPersonsDictionary.Count);

Prześledźmy teraz, jak można podejrzeć zawartość całej kolekcji, używając pętli Foreach:

foreach (KeyValuePair<int, Person> oPair in oPersonsDictionary)
{
    Console.WriteLine("Id osoby: " + oPair.Key +
        "\nImię: " + oPair.Value.FirstName +
        "\nNazwisko: " + oPair.Value.LastName +
        "\nWiek: " + oPair.Value.Age + " lat.");
}

Niestety w tym przypadku operacja trochę się komplikuje. Musimy skorzystać ze specjalnej struktury KeyValuePair, zawierającej pary klucza oraz wartości. Wewnątrz pętli odwołujemy się do nich odpowiednio używając Key oraz Value. Wewnątrz właściwości Value otrzymujemy dostęp do konkretnych elementów naszej klasy Person.

Kiedy skończymy zabawę, możemy wyczyścić zawartość kolekcji, oczywiście metodą Clear:

oPersonsDictionary.Clear();

Klasa Queue

Klasa Queue, czyli klasa kolejki bywa przydatna szczególnie w aplikacjach wielowątkowych, kiedy jeden wątek dodaje element, a drugi go zdejmuje. Można powiedzieć, że jest to jeden z kanonów informatyki. Kolejka działa według porządku FIFO czyli First In, First Out - pierwszy wszedł, pierwszy wyszedł. Spójrzmy na deklarację:

Queue<T>

Tym samym, nową kolejkę osób tworzymy w sposób następujący:

Queue<Person> oPersonsQueue = new Queue<Person>();

Teraz jednak zaczynają się niespodzianki, bo kolejki funkcjonują trochę inaczej aniżeli kolekcje przedstawione wyżej. Zamiast metody Add, mamy metodę Enqueue:

oPersonsQueue.Enqueue(new Person(1, "Jan", "Kowalski", 24));
oPersonsQueue.Enqueue(new Person(2, "Jan", "Nowak", 26));
oPersonsQueue.Enqueue(new Person(3, "Anna", "Gruszka", 28));

Aby zdjąć element z kolejki - pierwszy z dodanych, używamy do tego metody Dequeue:

Person oPerson = oPersonsQueue.Dequeue();

Metoda Dequeue zdejmie obiekt klasy Person z kolejki i umieści go w odpowiednim obiekcie. Przy zdejmowaniu elementów z kolejki, powinniśmy sprawdzić czy ilość elementów większa jest od zera. Oczywiście możemy to zrobić za pomocą metody Count. Sprawdzenie takie jest konieczne, ponieważ próba pobrania obiektów z pustej kolejki spowoduje wyjątek.

if (oPersonsQueue.Count > 0)
{
    Person oPerson = oPersonsQueue.Dequeue();
}

Jeśli chcemy tylko pobrać element z kolejki, nie zdejmując go, to potrzebna nam jest metoda Peek:

Person oPerson = oPersonsQueue.Peek();

Oczywiście i w tym przypadku należy sprawdzić czy kolejka nie jest pusta.

Aby wyświetlić zawartość kolejki, możemy skorzystać z pętli Foreach:

foreach (Person oPerson in oPersonsQueue)
{
    Console.WriteLine("Id osoby: " + oPerson.PersonId +
        "\nImię: " + oPerson.FirstName +
        "\nNazwisko: " + oPerson.LastName +
        "\nWiek: " + oPerson.Age + " lat.");
}

Podsumowanie

W dzisiejszym wpisie, przedstawiłem trzy różne klasy kolekcji, choć posiadające wiele wspólnych elementów. Przedstawiłem również szereg przydatnych metod, które mogą naprawdę ułatwić życie:) Tak więc zachęcam do ich użytkowania.

Data ostatniej modyfikacji: 07.06.2013, 14:59.

Podoba Ci się ten wpis? Powiedz o tym innym!

Send to Kindle

Komentarze

blog comments powered by Disqus