Typy generyczne

Adam Boduch

Typy generyczne to nowość wprowadzona w środowisku .NET Framework, w wersji 2.0 (poprzednia wersja 1.1 nie posiadała możliwości wykorzystania typów generycznych), wzorowana na technologii templates z języka C++. Typy generyczne posiadają szerokie zastosowanie, w szczególności jeżeli chodzi o wykorzystanie kolekcji.

Spójrz na poniższy kod prezentujący, w jaki sposób możemy dodać elementy do kolekcji typu ArrayList i wyświetlić je:

ArrayList Foo = new ArrayList();

Foo.Add("Adam");
Foo.Add("Marta");
Foo.Add(100);
Foo.Add(2.34);

for (int i = 0; i < Foo.Count; i++)
{
    Console.WriteLine("Indeks: {0} wartość: {1}", i, Foo[i]);
}

Przy pomocy Add() dodajemy do listy kolejne elementy, raz typu String, później liczbę stałoprzecinkową i wreszcie ? liczbę rzeczywistą. Jest to absolutnie dopuszczalne, gdyż parametr metody Add() jest typu Object, a jak wiadomo wszystkie typy .NET Framework dziedziczą po tym typie (konkretnie jest to alias do klasy System.Object).

Przy każdym wywołaniu metody Add() program musi wykonać opakowywanie (ang. boxing) typów, a przy wyświetlaniu - odpakowywanie. Przy dużej ilości danych trwa to dość długo, na tyle długo iż opłacalne jest wykorzystanie w tym momencie typów generycznych.

Dlatego jeżeli zamierzasz umieścić w kolekcji dane tego samego typu, lepiej wykorzystać klasy oferowane przez przestrzeń nazw System.Collection.Generic. Znajdują się tam klasy, odpowiedniki klas Stack oraz Queue (z przestrzeni System.Collection), które działają szybciej na danych tego samego typu. Nie ma w przestrzeni nazw System.Collection.Generic klasy ArrayList. Zamiast tego możemy jednak wykorzystać klasę List, która jest właściwie generycznym odpowiednikiem ArrayList. Listing prezentuje prosty przykład użycia klasy <code>List<T></code>:

using System;
using System.Collections.Generic;

namespace FooConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            List<int> Foo = new List<int>();

            Foo.Add(10);
            Foo.Add(100);
            Foo.Add(10000);
            
            for (int i = 0; i < Foo.Count; i++)
            {
                Console.WriteLine("Indeks: {0} wartość: {1}", i, Foo[i]);
            }
            Console.Read();

        }
    }
}

Przy deklarowaniu i utworzeniu obiektu klasy <code>List<T></code> musiałem podać typ danych (Int), na jakim chcemy operować. Oczywiście istnieje możliwość operowania na dowolnym typie danych ? wówczas w miejsce T należy podać jego nazwę.

Tworzenie typów generycznych

Istnieje możliwość tworzenia własnych klas generycznych. Jedyne, co musimy zrobić, to na końcu nazwy dodać frazę <code><T></code>:

class Generic<T>
{
}

Teraz przy tworzeniu egzemplarza klasy kompilator będzie wymagał, aby podać również typ danych, na których ma ona operować ? np.:

Generic<string> MyGeneric = new Generic<string>();

Przyjęło się, że przy deklarowaniu typów generycznych stosujemy frazę <T>. Kompilator nie wymusza jednak takiego nazewnictwa, więc równie dobrze możemy napisać: class Generic<Type> {}.

Po zadeklarowaniu takiej klasy w jej obrębie typ T będzie oznaczał typ danych podany podczas jej tworzenia. Można go dowolnie wykorzystywać. Np.:

class Generic<T>
{
    public void Add(T X)
    {
        Console.WriteLine("{0}", X);
    }
}

Obsługa takiej klasy wiąże się z odpowiednim utworzeniem obiektu:

Generic<string> MyGeneric = new Generic<string>();
MyGeneric.Add("Hello World!");

Metody generyczne

Istnieje również możliwość deklarowania metod generycznych. Ich tworzenie wygląda bardzo podobnie:

static void Foo<T>(T Bar)
{
    Console.WriteLine("{0}", Bar);
}

Wywołując taką metodę, możesz, aczkolwiek nie musisz, podawać typu danych - np.:

Foo("Adam"); // dobrze
Foo<int>(12); // dobrze

Kompilator domyśli się typu na podstawie przekazanych parametrów.

Korzystanie z list

Klasa List<T> posiada spore możliwości - implementując interfejsy <code>IList<T></code>, <code>ICollection<T></code>, <code>IEnumerable<T></code>, IList, ICollection, IEnumerable, umożliwia dodawanie, usuwanie dowolnych pozycji list. Zaprezentuję prostą aplikację przedstawiającą użycie list.

Aplikacja będzie dość prosta. Po uruchomieniu użytkownik będzie mógł dodać do listy nazwę województwa wraz z jego stolicą

csharp_listy.jpg
Rysunek. Aplikacja w trakcie działania

Użytkownik dodatkowo będzie mógł mieć możliwość usunięcia zaznaczonej pozycji oraz wyczyszczenia całej listy. W liście będą przechowywane dane odnośnie do województw oraz ich stolic. W tym celu zadeklarowałem odpowiednią strukturę:

public struct Location
{
    public string Province;
    public string Capital;
} 

Listing zawiera kod źródłowy głównego formularza programu:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace ListApp
{
    // struktura przechowywana w liście
    public struct Location
    {
        public string Province;
        public string Capital;
    }

    public partial class MainForm : Form
    {
        private List<Location> MyList;

        public MainForm()
        {
            InitializeComponent();
        }

        // metoda uaktualniająca stan komponentu ListBox zgodnie ze stanem faktycznym
        private void UpdateList()
        {
            lbLocation.Items.Clear();

            for (int i = 0; i < MyList.Count; i++)
            {
                lbLocation.Items.Add(
                    String.Format("{0,-10} {1,10}", MyList[i].Province, MyList[i].Capital
                ));
            }
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            // utworzenie instancji klasy w momencie załadowania formularza
            MyList = new List<Location>();
            lbLocation.Items.Clear();
        }

        private void btnClear_Click(object sender, EventArgs e)
        {
            MyList.Clear();
            UpdateList();
        }

        private void btnDelete_Click(object sender, EventArgs e)
        {
            // usunięcie zaznaczonej pozycji
            MyList.RemoveAt(lbLocation.SelectedIndex);
            UpdateList();
        }

        private void btnAdd_Click(object sender, EventArgs e)
        {
            Location MyLocation = new Location();

            MyLocation.Province = textProvince.Text;
            MyLocation.Capital = textCapitol.Text;

            textProvince.Text = textCapitol.Text = "";

            MyList.Add(MyLocation);
            UpdateList();
        }
    }
}

Myślę, że aplikacja jest dość prosta. Za wyświetlanie wszystkich pozycji w komponencie typu ListBox odpowiada metoda UpdateList(). W pętli dodaje ona kolejne pozycje do komponentu ListBox po uprzednim jego wyczyszczeniu. Odpowiednie procedury zdarzeniowe komponentów Button służą do dodawania nowej pozycji, usuwania zaznaczonej oraz czyszczenia wszystkich dodanych rekordów.

Po naciśnięciu przycisku służącego do dodawania nowych wpisów tworzona jest instancja struktury, do której przypisujemy dane wpisane w kontrolkach typu TextBox. Następnie przy pomocy metody Add() dodajemy nowy wpis do kolejki.

Zobacz też:

© [[User:Adam Boduch|Adam Boduch]]. Zabrania się rozpowszechniania tego tekstu bez zgody autora.

1 komentarz