Uso di indicizzatori (Guida per programmatori C#)

Gli indicizzatori sono una praticità sintattica che consente di creare una classe, uno struct o un'interfaccia a cui le applicazioni client possono accedere come matrice. Il compilatore genererà una Item proprietà (o una proprietà denominata in modo alternativo se IndexerNameAttribute presente) e i metodi di accesso appropriati. Gli indicizzatori sono in genere implementati in tipi il cui scopo principale è incapsulare una raccolta o una matrice interna. Si supponga, ad esempio, di avere una classe TempRecord che rappresenta la temperatura in Fahrenheit registrata in 10 momenti diversi durante un periodo di 24 ore. La classe contiene una temps matrice di tipo float[] per archiviare i valori di temperatura. Implementando un indicizzatore in questa classe, i client possono accedere alle temperature in un'istanza TempRecord come float temp = tempRecord[4] invece che come float temp = tempRecord.temps[4]. La notazione dell'indicizzatore non solo semplifica la sintassi per le applicazioni client; rende anche la classe e il suo scopo più intuitivo per altri sviluppatori di comprendere.

Per dichiarare un indicizzatore in una classe o uno struct, usare la parola chiave this, come nell'esempio seguente:

// Indexer declaration
public int this[int index]
{
    // get and set accessors
}

Importante

La dichiarazione di un indicizzatore genererà automaticamente una proprietà denominata Item sull'oggetto . La Item proprietà non è direttamente accessibile dall'espressione di accesso ai membri dell'istanza. Inoltre, se si aggiunge una proprietà personalizzata Item a un oggetto con un indicizzatore, verrà visualizzato un errore del compilatore CS0102. Per evitare questo errore, usare la ridenominazione dell'indicizzatore IndexerNameAttribute come descritto di seguito.

Osservazioni:

Il tipo di un indicizzatore e dei relativi parametri deve essere accessibile almeno quanto l'indicizzatore. Per altre informazioni sui livelli di accessibilità, vedere Modificatori di accesso.

Per altre informazioni sull'uso degli indicizzatori con un'interfaccia, vedere Indicizzatori nelle interfacce.

La firma di un indicizzatore è costituita dal numero e dai tipi dei relativi parametri formali. Non include il tipo di indicizzatore o i nomi dei parametri formali. Se si dichiarano più indicizzatori nella stessa classe, gli indicizzatori devono avere firme diverse.

Un indicizzatore non è classificato come variabile; pertanto, un valore dell'indicizzatore non può essere passato per riferimento (come ref parametro o out ) a meno che il relativo valore non sia un riferimento (ad esempio, restituisce per riferimento).

Per fornire all'indicizzatore un nome che possa essere usato da altri linguaggi, usare System.Runtime.CompilerServices.IndexerNameAttribute, come nell'esempio seguente:

// Indexer declaration
[System.Runtime.CompilerServices.IndexerName("TheItem")]
public int this[int index]
{
    // get and set accessors
}

Questo indicizzatore avrà il nome TheItem, perché viene sottoposto a override dall'attributo del nome dell'indicizzatore. Per impostazione predefinita, il nome dell'indicizzatore è Item.

Esempio 1

Nell'esempio seguente viene illustrato come dichiarare un campo di matrice privata, temps, e un indicizzatore. L'indicizzatore consente l'accesso diretto all'istanza tempRecord[i]. In alternativa all'uso dell'indicizzatore, è possibile dichiarare la matrice come un membro public e accedere direttamente ai relativi membri, tempRecord.temps[i].

public class TempRecord
{
    // Array of temperature values
    float[] temps =
    [
        56.2F, 56.7F, 56.5F, 56.9F, 58.8F,
        61.3F, 65.9F, 62.1F, 59.2F, 57.5F
    ];

    // To enable client code to validate input
    // when accessing your indexer.
    public int Length => temps.Length;
    
    // Indexer declaration.
    // If index is out of range, the temps array will throw the exception.
    public float this[int index]
    {
        get => temps[index];
        set => temps[index] = value;
    }
}

Si noti che quando viene valutato l'accesso di un indicizzatore, ad esempio in un'istruzione Console.Write, viene richiamata la funzione di accesso get. Pertanto, se non esiste alcuna funzione di accesso get, si verifica un errore in fase di compilazione.

var tempRecord = new TempRecord();

// Use the indexer's set accessor
tempRecord[3] = 58.3F;
tempRecord[5] = 60.1F;

// Use the indexer's get accessor
for (int i = 0; i < 10; i++)
{
    Console.WriteLine($"Element #{i} = {tempRecord[i]}");
}

Indicizzazione tramite altri valori

C# non limita il tipo del parametro dell'indicizzatore a Integer. Può ad esempio essere utile usare una stringa con un indicizzatore. Un indicizzatore di questo tipo potrebbe essere implementato eseguendo la ricerca della stringa nella raccolta e restituendo il valore appropriato. Poiché è possibile eseguire l'overload delle funzioni di accesso, le versioni stringa e integer possono coesistere.

Esempio 2

L'esempio seguente dichiara una classe che archivia i giorni della settimana. Una funzione di accesso get accetta una stringa e il nome di un giorno e restituisce l'intero corrispondente. Ad esempio, "Domenica" restituisce 0, "Lunedì" restituisce 1 e così via.

// Using a string as an indexer value
class DayCollection
{
    string[] days = ["Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat"];

    // Indexer with only a get accessor with the expression-bodied definition:
    public int this[string day] => FindDayIndex(day);

    private int FindDayIndex(string day)
    {
        for (int j = 0; j < days.Length; j++)
        {
            if (days[j] == day)
            {
                return j;
            }
        }

        throw new ArgumentOutOfRangeException(
            nameof(day),
            $"Day {day} is not supported.\nDay input must be in the form \"Sun\", \"Mon\", etc");
    }
}

Utilizzo dell'esempio 2

var week = new DayCollection();
Console.WriteLine(week["Fri"]);

try
{
    Console.WriteLine(week["Made-up day"]);
}
catch (ArgumentOutOfRangeException e)
{
    Console.WriteLine($"Not supported input: {e.Message}");
}

Esempio 3

Nell'esempio seguente viene dichiarata una classe che archivia i giorni della settimana usando l'enumerazione System.DayOfWeek . Una get funzione di accesso accetta un DayOfWeekoggetto , il valore di un giorno e restituisce l'intero corrispondente. Ad esempio, restituisce DayOfWeek.Sunday 0, DayOfWeek.Monday restituisce 1 e così via.

using Day = System.DayOfWeek;

class DayOfWeekCollection
{
    Day[] days =
    [
        Day.Sunday, Day.Monday, Day.Tuesday, Day.Wednesday,
        Day.Thursday, Day.Friday, Day.Saturday
    ];

    // Indexer with only a get accessor with the expression-bodied definition:
    public int this[Day day] => FindDayIndex(day);

    private int FindDayIndex(Day day)
    {
        for (int j = 0; j < days.Length; j++)
        {
            if (days[j] == day)
            {
                return j;
            }
        }
        throw new ArgumentOutOfRangeException(
            nameof(day),
            $"Day {day} is not supported.\nDay input must be a defined System.DayOfWeek value.");
    }
}

Utilizzo dell'esempio 3

var week = new DayOfWeekCollection();
Console.WriteLine(week[DayOfWeek.Friday]);

try
{
    Console.WriteLine(week[(DayOfWeek)43]);
}
catch (ArgumentOutOfRangeException e)
{
    Console.WriteLine($"Not supported input: {e.Message}");
}

Programmazione efficiente

Esistono due modi principali per migliorare la sicurezza e l'affidabilità degli indicizzatori:

  • Assicurarsi di incorporare qualche tipo di strategia di gestione degli errori per gestire la possibilità che il codice client passi un valore di indice non valido. Nel primo esempio riportato in questo argomento, la classe TempRecord fornisce una proprietà Length che consente al codice client di verificare l'input prima di passarlo all'indicizzatore. È anche possibile inserire il codice di gestione degli errori nell'indicizzatore stesso. Assicurarsi di documentare per gli utenti qualsiasi eccezione generata all'interno di una funzione di accesso dell'indicizzatore.

  • Impostare l'accessibilità delle funzioni di accesso get e set in modo che siano quanto più restrittive possibile. Questo è particolarmente importante per la funzione di accesso set. Per altre informazioni, vedere Limitazione dell'accessibilità delle funzioni di accesso.

Vedi anche