MySQL Java JavaScript PHP Python HTML-CSS C-sharp

Indexatori

Indexatorii permit indexarea obiectelor și accesarea datelor prin intermediul unui index. Practic, cu ajutorul indexatorilor putem lucra cu obiectele ca și cu array-urile. Ca formă, indexatorii seamănă cu proprietățile, având blocuri standard get și set, care returnează și atribuie valori.

Definirea formală a unui indexator:

return_type this [parameter_type parameter1, ...]
{
   get { ... }
   set { ... }
}

Spre deosebire de proprietăți, un indexator nu are un nume. În loc de aceasta, se folosește cuvântul cheie this, după care urmează parametrii în paranteze pătrate. Un indexator trebuie să aibă cel puțin un parametru.

Exemplu:

Să presupunem că avem clasa Person, care reprezintă o persoană, și clasa Company, care reprezintă o companie unde lucrează oameni. Vom folosi indexatori pentru a defini clasa Company:

class Person
{
   public string Name { get; }
   public Person(string name) => Name = name;
}

class Company
{
   Person[] personal;
   public Company(Person[] people) => personal = people;

   // indexator
   public Person this[int index]
   {
       get => personal[index];
       set => personal[index] = value;
   }
}

Pentru stocarea personalului companiei, în clasă este definit un array personal, care constă din obiecte Person. Pentru accesarea acestor obiecte este definit un indexator:

public Person this[int index]

Indexatorul este similar unei proprietăți standard. În primul rând, se definește tipul indexatorului, în acest caz tipul Person. Tipul indexatorului determină ce obiecte va returna și primi indexatorul.

În al doilea rând, indexatorul are un parametru int index, prin care se accesează elementele din interiorul obiectului Company.

Pentru returnarea unui obiect în indexator este definit blocul get:

get => personal[index];

Deoarece indexatorul are tipul Person, în blocul get trebuie să returnăm un obiect de acest tip folosind operatorul return. Aici putem defini o logică variată. În acest caz, returnăm pur și simplu obiectul din array-ul personal.

În blocul set, la fel ca în cazul unei proprietăți obișnuite, obținem prin parametrul value obiectul Person transmis și îl stocăm în array la indexul specificat.

set => personal[index] = value;

După aceasta, putem lucra cu obiectul Company ca și cu un set de obiecte Person:

var microsoft = new Company(new[]
{
   new Person("Tom"), new Person("Bob"), new Person("Sam"), new Person("Alice")
});
// obținem un obiect din indexator
Person firstPerson = microsoft[0];
Console.WriteLine(firstPerson.Name);  // Tom
// înlocuim obiectul
microsoft[0] = new Person("Mike");
Console.WriteLine(microsoft[0].Name);  // Mike

Este de remarcat faptul că, dacă indexatorului i se va transmite un index incorect, care nu există în array-ul personal, vom obține o excepție, la fel ca în cazul accesării directe a elementelor array-ului. În acest caz, putem prevedea o logică suplimentară, de exemplu, verificarea indexului transmis:

class Company
{
   Person[] personal;
   public Company(Person[] people) => personal = people;

   // indexator
   public Person this[int index]
   {
       get
       {
           // dacă indexul există în array
           if (index >= 0 && index < personal.Length)
               return personal[index]; // returnăm obiectul Person la index
           else
               throw new ArgumentOutOfRangeException(); // generăm o excepție
       }
       set
       {
           // dacă indexul există în array
           if (index >= 0 && index < personal.Length)
               personal[index] = value; // înlocuim valoarea la index
       }
   }
}

Aici, în blocul get, dacă indexul transmis există în array, returnăm obiectul la acel index. Dacă indexul nu există în array, generăm o excepție. Similar, în blocul set, setăm valoarea la index, dacă indexul există în array.

Indici

Indexatorul primește un set de indici sub forma unor parametri. Cu toate acestea, indicii nu trebuie neapărat să fie de tip int, iar valorile returnate/setate nu trebuie neapărat să fie stocate într-un array.

De exemplu, putem considera un obiect ca un depozit de atribute/proprietăți și să transmitem numele atributului sub forma unui șir de caractere:

User tom = new User();
// setăm valorile
tom["name"] = "Tom";
tom["email"] = "tom@gmail.com";
tom["phone"] = "+1234556767";

// obținem valoarea
Console.WriteLine(tom["name"]); // Tom

class User
{
   string name = "";
   string email = "";
   string phone = "";
   public string this[string propname]
   {
       get
       {
           switch (propname)
           {
               case "name": return name;
               case "email": return email;
               case "phone": return phone;
               default: throw new Exception("Unknown Property Name");
           }
       }
       set
       {
           switch (propname)
           {
               case "name":
                   name = value;
                   break;
               case "email":
                   email = value;
                   break;
               case "phone":
                   phone = value;
                   break;
           }
       }
   }
}

În acest caz, indexatorul din clasa User primește ca index un șir de caractere, care stochează numele atributului (în acest caz, numele câmpului clasei).

În blocul get, în funcție de valoarea indexului de tip șir, se returnează valoarea unui anumit câmp al clasei. Dacă este transmis un nume necunoscut, se generează o excepție. În blocul set, logica este similară - după index aflăm pentru ce câmp trebuie să setăm valoarea.

Utilizarea mai multor parametri

Un indexator poate primi mai mulți parametri. Să presupunem că avem o clasă, în care depozitul este definit sub forma unui array bidimensional sau matrice:

class Matrix
{
   int[,] numbers = new int[,] { { 1, 2, 4 }, { 2, 3, 6 }, { 3, 4, 8 } };
   public int this[int i, int j]
   {
       get => numbers[i, j];
       set => numbers[i, j] = value;
   }
}

Acum, pentru definirea indexatorului sunt folosiți doi indici - i și j. În program, trebuie să accesăm obiectul folosind doi indici:

Matrix matrix = new Matrix();
Console.WriteLine(matrix[0, 0]);
matrix[0, 0] = 111;
Console.WriteLine(matrix[0, 0]);

Este important de menționat că indexatorul nu poate fi static și se aplică doar instanțelor clasei. Totuși, indexatorii pot fi virtuali și abstracti și pot fi suprascriși în clasele derivate.

Blocurile get și set

La fel ca în cazul proprietăților, în indexatori se pot omite blocurile get sau set, dacă nu sunt necesare. De exemplu, putem elimina blocul set și face indexatorul disponibil doar pentru citire:

class Matrix
{
   int[,] numbers = new int[,] { { 1, 2, 4 }, { 2, 3, 6 }, { 3, 4, 8 } };
   public int this[int i, int j]
   {
       get => numbers[i, j];
   }
}

De asemenea, putem limita accesul la blocurile get și set folosind modificatori de acces. De exemplu, putem face blocul set privat:

class Matrix
{
   int[,] numbers = new int[,] { { 1, 2, 4 }, { 2, 3, 6 }, { 3, 4, 8 } };
   public int this[int i, int j]
   {
       get => numbers[i, j];
       private set => numbers[i, j] = value;
   }
}

Suprascrierea indexatorilor

Similar metodelor, indexatorii pot fi suprascrisi. În acest caz, indexatorii trebuie să difere prin numărul, tipul sau ordinea parametrilor utilizați. De exemplu:

var microsoft = new Company(new Person[]

{ new("Tom"), new("Bob"), new("Sam") });

Console.WriteLine(microsoft[0].Name);      // Tom
Console.WriteLine(microsoft["Bob"].Name);  // Bob

class Person
{
   public string Name { get; }
   public Person(string name) => Name = name;
}

class Company
{
   Person[] personal;
   public Company(Person[] people) => personal = people;

   // indexator
   public Person this[int index]
   {
       get => personal[index];
       set => personal[index] = value;
   }

   public Person this[string name]
   {
       get
       {
           foreach (var person in personal)
           {
               if (person.Name == name) return person;
           }
           throw new Exception("Unknown name");
       }
   }
}

În acest caz, clasa Company conține două versiuni ale indexatorului. Prima versiune obține și setează obiectul Person prin index, iar a doua obține obiectul Person după numele său.

← Lecția anterioară Lecția următoare →