CodeGym /Corsi /C# SELF /Introduzione agli indexer

Introduzione agli indexer

C# SELF
Livello 18 , Lezione 0
Disponibile

1. Due parole sulle collezioni

Finora abbiamo lavorato con gli array — è il modo più semplice per conservare più elementi dello stesso tipo. Ma in C# ci sono un sacco di collezioni più comode e potenti, che risolvono vari problemi.

Collezione — è un oggetto che può contenere un gruppo di altri oggetti. A differenza degli array, le collezioni spesso possono cambiare la loro dimensione dinamicamente, offrono metodi comodi per lavorare con i dati e sono ottimizzate per diversi scenari d'uso.

Tipi principali di collezioni (panoramica veloce):

List<T> — array dinamico che può crescere e restringersi:

List<string> names = new List<string>();
names.Add("Alessio");
names.Add("Maria");
Console.WriteLine(names[0]); // Alessio
Console.WriteLine(names.Count); // 2

Dictionary<TKey, TValue> — collezione di coppie "chiave-valore", dove ad ogni chiave corrisponde un valore:

Dictionary<string, int> ages = new Dictionary<string, int>();
ages["Alessio"] = 25;
ages["Maria"] = 30;
Console.WriteLine(ages["Alessio"]); // 25

Nota: negli esempi sopra usiamo le parentesi quadre [] per accedere agli elementi delle collezioni — proprio come con gli array! Questo è possibile grazie agli indexer, di cui parleremo oggi.

Questa è solo una prima occhiata alle collezioni — vedremo in dettaglio le loro possibilità, differenze e applicazioni nelle prossime lezioni. Ora è importante capire che le collezioni permettono di accedere ai loro elementi tramite parentesi quadre, e non è magia, ma un meccanismo speciale del linguaggio.

2. Indexer

Gli oggetti C# normali funzionano tramite proprietà e metodi. Ma che fare se il tuo oggetto è una specie di mini-collezione? Per esempio, immagina:

  • Stai scrivendo una classe Week, che deve restituire il nome del giorno della settimana tramite numero: week[0] → "Lunedì".
  • Oppure una classe Library, dove puoi accedere a un libro tramite il suo numero: library[3] → "Verso Swann".

Comodo, vero? Sarebbe strano scrivere library.GetBookByIndex(3) ogni volta — vuoi accedere al tuo oggetto come a un array!

Ed è qui che entrano in gioco gli indexer.

Indexer — è un membro speciale della classe che permette di usare gli oggetti di quella classe con la sintassi delle parentesi quadre, come gli array: obj[0], obj["chiave"], e così via.

Un indexer sembra una proprietà, ma invece del nome — prende parametri tra parentesi quadre. È simile a quando scriviamo .Name, solo che qui usiamo [i].

3. Una collezione semplice con indexer

Mettiamo insieme una mini-classe che conserva i colori preferiti. Usiamo un array di stringhe per conservarli. Senza indexer dovresti fare un metodo tipo GetColor(int i). Ma con l'indexer:


using System;

public class FavoriteColors
{
   // Campo privato per conservare i colori
   private string[] colors = new string[5];

   // Indexer:
   public string this[int index]
   {
       get
       {
           // Controllo dei limiti dell'array (incapsulamento in azione!)
           if (index < 0 || index >= colors.Length)
               throw new IndexOutOfRangeException("Indice colore non valido!");

           return colors[index];
       }
       set
       {
           if (index < 0 || index >= colors.Length)
               throw new IndexOutOfRangeException("Indice colore non valido!");

           colors[index] = value ?? throw new ArgumentNullException(nameof(value));
       }
   }
}

class Program
{
   static void Main()
   {
       FavoriteColors favorites = new FavoriteColors();

       favorites[0] = "Verde";
       favorites[1] = "Blu";
       favorites[2] = "Rosso";
       favorites[10] = "Viola"; // Lancia eccezione!

       Console.WriteLine(favorites[1]); // Blu
   }
}

Cosa succede qui?

  • Creiamo un array privato, così nessuno da fuori può giocarci direttamente.
  • L'indexer è definito come public string this[int index], dove this — parola chiave che indica che l'indexer appartiene all'oggetto.
  • Dentro get e set controlliamo il range, non lasciamo uscire dai limiti/scrivere null.
  • Alla fine possiamo fare favorites[0], come con un array normale.

4. Sintassi dell'indexer: dettagli

La sintassi è simile a una proprietà, ma invece del nome (tipo Age) si usa la parola chiave this con parametri:


// Firma dell'indexer (schema generale)
[modificatore] TipoRisultato this[TipoIndice nomeIndice]
{
   get { ... }
   set { ... }
}

Esempio: Classico

public class MyCollection
{
   private int[] data = new int[10];

   // Indexer per lettura e scrittura
   public int this[int index]
   {
       get { return data[index]; }
       set { data[index] = value; }
   }
}

Gli indexer non sono solo per int

La cosa bella: l'indexer non deve per forza accettare solo int. Puoi usare qualsiasi tipo (basta che abbia senso accedere tramite chiave):

public string this[string colorName]
{
   get { /* ... */ }
   set { /* ... */ }
}

Per esempio, in una rubrica telefonica ha senso cercare per nome:

public class PhoneBook
{
   private Dictionary<string, int> entries = new Dictionary<string, int>();

   public int this[string name]
   {
       get
       {
           if (entries.ContainsKey(name))
               return entries[name];
           return null;
       }
       set
       {
           entries[name] = value;
       }
   }
}

Delle collezioni e di come funziona Dictionary<string, string> parlerò nelle prossime lezioni :P

5. Esempio pratico: Contatore di parole in un testo

Continuiamo a sviluppare la nostra app. Supponiamo che ora abbiamo una classe che conta quante volte ogni parola appare in un testo. È comodo se l'utente può accedere all'oggetto tramite parentesi quadre per ottenere il conteggio per parola:


using System.Collections.Generic;

public class WordCounter
{
   private Dictionary<string, int> counter = new Dictionary<string, int>();

   // Indexer per stringa (parola)
   public int this[string word]
   {
       get
       {
           if (counter.ContainsKey(word))
               return counter[word];
           return 0; // Se la parola non c'è, ritorna 0.
       }
       set
       {
           counter[word] = value;
       }
   }

   // Metodo per contare le parole da una stringa
   public void AddWords(string text)
   {
       foreach (var word in text.Split(' ', System.StringSplitOptions.RemoveEmptyEntries))
       {
           if (counter.ContainsKey(word))
               counter[word]++;
           else
               counter[word] = 1;
       }
   }
}

// In Main:
var wc = new WordCounter();
wc.AddWords("mamma lavava finestra lavava mamma papà");
Console.WriteLine($"'mamma' appare {wc["mamma"]} volta(e)");
Console.WriteLine($"'finestra' appare {wc["finestra"]} volta(e)");
Console.WriteLine($"'gatto' appare {wc["gatto"]} volta(e)"); // 0

A cosa serve nella pratica? Questo approccio si usa spesso per implementare collezioni personalizzate, librerie di memoria, mapping (dizionari e indici) e persino DSL (linguaggi specializzati dentro C#).

6. Limiti e sfumature

Gli indexer sono potenti, ma ci sono alcune regole e trappole (ovvio!).

L'indexer non ha nome

A differenza di una proprietà, l'indexer non ha nome, solo la firma tipo this[tipoparametro]. Se provi a scrivere public int MyIndexer[int i] — il compilatore si arrabbia. Usiamo solo this.

Non esistono indexer statici

Gli indexer sono sempre creati per un'istanza della classe, non per membri statici. Quindi non puoi dichiarare static int this[int i] — la logica è che this punta sempre a una specifica istanza dell'oggetto.

Possono essere overloadati per tipo/numero di parametri

Puoi creare più indexer in una classe, se i loro parametri sono diversi per tipo o quantità. Per esempio:

public string this[int i] { get { ... } set { ... } }
public string this[string key] { get { ... } set { ... } }

È legale, il compilatore non si confonde — e ti avvisa se i parametri si sovrappongono.

Supporto solo tramite proprietà

Non puoi dichiarare un indexer senza accessor get o set. Se vuoi solo lettura — togli set, solo scrittura — get. Di solito si usano entrambi.

7. Utilità pratica e perché saperlo

  • Gli indexer sono usati tantissimo nelle collezioni di dati. Molte classi .NET li hanno: per esempio, List<T>, Dictionary<TKey,TValue>. Quando scrivi list[2], stai usando proprio l'indexer!
  • Gli indexer permettono di nascondere l'implementazione interna (incapsulamento!), ma offrire un'interfaccia comoda e familiare. L'utente della tua classe non pensa a come conservi i dati, usa solo le solite [index].
  • Il tuo codice diventa compatto e intuitivo — cosa molto apprezzata ai colloqui (e dai tuoi futuri colleghi).

Proprietà e Indexer: confronto

Proprietà Indexer
Nome Sì (per esempio, Name) No (invece del nome — this[parametro])
Accesso Per nome Per indice (o altra chiave)
Statica Può essere static Solo per istanza
Più in una classe Sì, quanti vuoi Sì, ma con firma dei parametri diversa
Uso Conservazione/accesso ai dati Mini-collezioni, dati associativi

8. Errori tipici e consigli

Errore n°1: tentare di fare un indexer static.
Non si può — this[...] funziona solo con l'oggetto.

Errore n°2: dimenticare il controllo dell'indice.
Se non controlli i limiti dell'array in get o set, il programma può crashare.

Errore n°3: tipi di parametri confusi.
Se crei due indexer con gli stessi parametri, il compilatore dà errore.

Errore n°4: dimenticare di implementare get o set.
Se vuoi accesso sia in lettura che in scrittura — entrambi devono essere implementati.

Consiglio: se la tua classe incapsula un array — passa semplicemente le chiamate all'array tramite l'indexer. Così il codice è più veloce e intuitivo.

9. Perché sapere tutto questo

Gli indexer rendono l'interfaccia dell'oggetto semplice, chiara e comoda. Permettono di nascondere l'implementazione interna, ma lasciare allo sviluppatore un modo pratico per accedere ai dati.

Proprio così sono fatte le collezioni integrate: string, List<T>, Dictionary<TKey, TValue>, Span<T> e molte altre. Quando scrivi array[2] o text[0], stai già usando un indexer.

E la cosa più importante — ora puoi scrivere le tue classi che funzionano in modo altrettanto flessibile e compatto. Quindi — sei un passo più vicino a scrivere codice professionale e leggibile.

2
Compito
C# SELF, livello 18, lezione 0
Bloccato
Creazione della classe "Week" con indicizzatore per i giorni della settimana
Creazione della classe "Week" con indicizzatore per i giorni della settimana
2
Compito
C# SELF, livello 18, lezione 0
Bloccato
Rubrica telefonica con indicizzatore per nome
Rubrica telefonica con indicizzatore per nome
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION