Indeksery i []Część NR.3

Zostawiając już bity w int-ach przejdę do innego wykorzystania indekserów (czy jak to się odmienia w języku polskim). Zanim stworzę swój przykład zbliżony do działania HashTable napiszę dlaczego indeksory są w ogóle używane w takich wypadkach.

Jest możliwe dla właściwości aby zwracała ona tablicę ,ale pamiętaj o tym ,że tablica jest typem referencyjnym więc poprzez udostępnianie tablicy w bezpośredni sposób narażasz na przypadkowe nadpisanie danych.

Oto przykład struktury, która udostępnia tablicę int dla właściwości, którą nazywałem Dane:

struct Struktura
{
    private int[] dane;

    public int[] Dane
    {
       get { return dane; }
       set { dane = value; }
    }
}

Teraz jak wygląda użycie tej właściwości. Kod nieprawidłowy ,ale to tylko przykład.

Struktura str = new Struktura();
int[] mojeDane = str.Dane;
mojeDane[0]++;
mojeDane[1]--;

Nie wygląda to szkodliwie na pierwszy rzut oka. Jednakże ponieważ tablice są typami referencyjnymi zmienna mojaDane jest referencją do tego samego obiektu, który jest prywatny w strukturze.

Jakakolwiek zmiana elementu mojeDane zmieni tablice dane.Wrażenie mojeDane[0]++ jest takie same jak dane[0]++. Tak nie powinno być.

Powinniśmy zwracać kopię ,a nie referencje we właściwości .W przeciwnym wypadku równie dobrze możemy mieć to pole publiczne skoro właściwość jej nie ukrywa. Zasada hermetyzacji idzie do kosza w takim wypadku.

Aby zwrócić kopię elementu zastosowałem metodę “Clone” ,a potem muszę wykonać rzutowanie ponieważ ta metoda zwraca typ object.

struct Struktura
{
    private int[] dane;

    public int[] Dane
    {
        get { return dane.Clone() as int[]; }
        set { dane = value.Clone() as int[]; }
    }
}

Te rozwiązanie jest oczywiście koszmarem dla pamięci w zależności od wielkości tablicy.

Indeksery wprowadzają rozwiązanie do tego problemu. Indeksery nie udostępniają całej tablicy jak właściwości - sprawiają one ,że każdy indywidualny element jest dostępny.

Wygląda to tak.

struct Struktura
{
    private int[] dane;

    public int this [int i]
    {
       get { return dane[i]; }
       set { dane[i] = value; }
    }
}

Do kodu wypadałoby jeszcze dodać konstruktor oraz zabezpieczenie przed wykroczeniem tablic poza indeks. A co tam wyrzucę jeszcze raz wyjątek tym razem jawnie. :)

struct Struktura
{
    public Struktura(int wielkoscTab)
    {
       dane = new int[wielkoscTab];
    }

    private int[] dane;

    public int this [int i]
    {
       get {
           if ((dane.Length > i) && (i > 0) )
                return dane[i];
           else throw new ArgumentException();
       }
       set {
           if ((dane.Length > i)  && (i > 0) ))
                dane[i] = value;
       }
    }
}

Użycie indekseru wygląda tak:

Struktura str = new Struktura(3);
int[]  mojeDane = new int[3];

str[0] = mojeDane[0]++;
str[1] = mojeDane[1]--;
str[2] = 15;   

Jak widać obie tablicę mają różne wartości.

indekser
Jest to bezpieczny i bardziej czytelny sposób.