Operatori ed espressioni di accesso ai membri: operatori punto, indicizzatore e chiamata.

È possibile usare diversi operatori ed espressioni per accedere a un membro di tipo. Questi operatori includono l'accesso ai membri (.), l'elemento matrice o l'accesso dell'indicizzatore ([]), index-from-end (^), intervallo (..), operatori condizionali Null (?. e ?[]) e chiamata al metodo (()). Tra questi sono inclusi gli operatori di accesso ai membri condizionali Null (?.) e l'accesso dell'indicizzatore (?[]).

Espressione di accesso membri .

Si usa il token . per accedere a un membro di uno spazio dei nomi o di un tipo, come illustrano gli esempi seguenti:

  • Usare . per accedere a uno spazio dei nomi annidato in uno spazio dei nomi, come illustra l'esempio seguente di direttiva using:
using System.Collections.Generic;
  • Usare . per formare un nome qualificato per accedere a un tipo in uno spazio dei nomi, come illustra il codice seguente:
System.Collections.Generic.IEnumerable<int> numbers = [1, 2, 3];

Usare una direttiva using per rendere facoltativo l'uso di nomi qualificati.

  • Usare . per accedere ai membri dei tipi, statici e non statici, come illustra il codice seguente:
List<double> constants =
[
    Math.PI,
    Math.E
];
Console.WriteLine($"{constants.Count} values to show:");
Console.WriteLine(string.Join(", ", constants));
// Output:
// 2 values to show:
// 3.14159265358979, 2.71828182845905

È anche possibile usare . per accedere a un metodo di estensione.

Operatore indicizzatore []

Le parentesi quadre [] vengono in genere usate per l'accesso agli elementi matrice, indicizzatore o puntatore.

Accesso a matrici

Nell'esempio seguente viene illustrato come accedere agli elementi matrice:

int[] fib = new int[10];
fib[0] = fib[1] = 1;
for (int i = 2; i < fib.Length; i++)
{
    fib[i] = fib[i - 1] + fib[i - 2];
}
Console.WriteLine(fib[fib.Length - 1]);  // output: 55

double[,] matrix = new double[2,2];
matrix[0,0] = 1.0;
matrix[0,1] = 2.0;
matrix[1,0] = matrix[1,1] = 3.0;
var determinant = matrix[0,0] * matrix[1,1] - matrix[1,0] * matrix[0,1];
Console.WriteLine(determinant);  // output: -3

Se un indice di matrice è fuori dai limiti della dimensione corrispondente di una matrice, viene generata un'eccezione IndexOutOfRangeException.

Come illustrato nell'esempio precedente, si usano le parentesi quadre anche per dichiarare un tipo di matrice o creare un'istanza di matrice.

Per altre informazioni sulle matrici, vedere Matrici.

Accesso all'indicizzatore

Nell'esempio seguente viene usato il tipo .NET Dictionary<TKey,TValue> per dimostrare l'accesso all'indicizzatore:

var dict = new Dictionary<string, double>();
dict["one"] = 1;
dict["pi"] = Math.PI;
Console.WriteLine(dict["one"] + dict["pi"]);  // output: 4.14159265358979

Gli indicizzatori consentono di indicizzare le istanze di un tipo definito dall'utente con modalità simili a quelle dell'indicizzazione della matrice. A differenza degli indici di matrice, che devono essere valori interi, i parametri dell'indicizzatore possono essere dichiarati di qualsiasi tipo.

Per altre informazioni sugli indicizzatori, vedere Indicizzatori.

Altri utilizzi di []

Per informazioni sull'accesso agli elementi del puntatore, vedere la sezione Operatore [] (accesso agli elementi del puntatore) dell'articolo Operatori relativi al puntatore.

Le parentesi quadre possono anche essere usate per specificare attributi:

[System.Diagnostics.Conditional("DEBUG")]
void TraceMethod() {}

Operatori condizionali Null: ?. e ?[]

Un operatore condizionale Null applica un'operazione di accesso ai membri (?.) o di accesso a elementi (?[]) al relativo operando solo se l'operando restituisce un valore diverso da Null, in caso contrario restituisce null. Ovvero:

  • Se a restituisce null, il risultato di a?.x o a?[x] è null.

  • Se a restituisce un valore diverso da Null, il risultato di a?.x o a?[x] corrisponde rispettivamente al risultato di a.x o a[x].

    Nota

    Se a.x o a[x] genera un'eccezione, a?.x o a?[x] genererebbe la stessa eccezione per non Null a. Ad esempio, se a è un'istanza di matrice non Null e x non rientra nei limiti di a, a?[x] genererà IndexOutOfRangeException.

Gli operatori condizionali Null causano corto circuiti. Vale a dire, se un'operazione in una catena di operazioni condizionali di accesso a un membro o a un elemento restituisce null, l'esecuzione delle altre operazioni della catena viene interrotta. Nell'esempio seguente B non viene valutato se A restituisce null e C non viene valutato se A o B restituisce null:

A?.B?.Do(C);
A?.B?[C];

Se A non è Null, ma B e C non è Null se A non è Null, è sufficiente applicare l'operatore condizionale Null a A:

A?.B.C();

Nell'esempio precedente, B non viene valutato e C() non viene chiamato se A è Null. Tuttavia, se l'accesso ai membri concatenati viene interrotto, ad esempio da parentesi come in (A?.B).C(), il corto circuito non si verifica.

I seguenti esempi illustrano l'uso degli operatori ?. e ?[]:

double SumNumbers(List<double[]> setsOfNumbers, int indexOfSetToSum)
{
    return setsOfNumbers?[indexOfSetToSum]?.Sum() ?? double.NaN;
}

var sum1 = SumNumbers(null, 0);
Console.WriteLine(sum1);  // output: NaN

List<double[]?> numberSets =
[
    [1.0, 2.0, 3.0],
    null
];

var sum2 = SumNumbers(numberSets, 0);
Console.WriteLine(sum2);  // output: 6

var sum3 = SumNumbers(numberSets, 1);
Console.WriteLine(sum3);  // output: NaN
namespace MemberAccessOperators2;

public static class NullConditionalShortCircuiting
{
    public static void Main()
    {
        Person? person = null;
        person?.Name.Write(); // no output: Write() is not called due to short-circuit.
        try
        {
            (person?.Name).Write();
        }
        catch (NullReferenceException)
        {
            Console.WriteLine("NullReferenceException");
        }; // output: NullReferenceException
    }
}

public class Person
{
    public required FullName Name { get; set; }
}

public class FullName
{
    public required string FirstName { get; set; }
    public required string LastName { get; set; }
    public void Write() => Console.WriteLine($"{FirstName} {LastName}");
}

Il primo dei due esempi precedenti usa anche l'operatore di coalescenza di valori Null ?? per specificare un'espressione alternativa da valutare nel caso in cui il risultato di un'operazione condizionale Null sia null.

Se a.x o a[x] è di un tipo valore che ammette i valori Null T, a?.x o a?[x] è del tipo di valore che ammette i valori Null corrispondenteT?. Se è necessaria un'espressione di tipo T, applicare l'operatore di coalescenza di valori Null ?? a un'espressione condizionale null, come illustrato nell'esempio seguente:

int GetSumOfFirstTwoOrDefault(int[]? numbers)
{
    if ((numbers?.Length ?? 0) < 2)
    {
        return 0;
    }
    return numbers[0] + numbers[1];
}

Console.WriteLine(GetSumOfFirstTwoOrDefault(null));  // output: 0
Console.WriteLine(GetSumOfFirstTwoOrDefault([]));  // output: 0
Console.WriteLine(GetSumOfFirstTwoOrDefault([3, 4, 5]));  // output: 7

Nell'esempio precedente, se non si usa l'operatore ??, numbers?.Length < 2 restituisce false quando numbers è null.

Nota

L'operatore ?. valuta l'operando sinistro non più di una volta, garantendo che non possa essere modificato in null dopo essere stato verificato come non Null.

L'operatore di accesso ai membri condizionale Null ?. è anche noto come operatore Elvis.

Chiamata a delegati thread-safe

Usare l'operatore ?. per controllare se un delegato è diverso da Null e richiamarlo in un modo thread-safe, ad esempio, quando si genera un evento, come illustrato nel codice seguente:

PropertyChanged?.Invoke(…)

Tale codice equivale al codice seguente:

var handler = this.PropertyChanged;
if (handler != null)
{
    handler(…);
}

L'esempio precedente è un modo thread-safe per garantire che venga richiamato solo un valore diverso da Null handler. Poiché le istanze del delegato non sono modificabili, nessun thread può modificare l'oggetto a cui fa riferimento la variabile locale handler. In particolare, se il codice eseguito da un altro thread annulla la sottoscrizione all'evento PropertyChanged e PropertyChanged diventa null prima che venga richiamato handler, l'oggetto a cui fa riferimento handler rimane invariato.

Espressione di chiamata ()

Usare le parentesi, (), per chiamare un metodo oppure richiamare un delegato.

L'esempio seguente illustra come chiamare un metodo, con o senza argomenti, e come richiamare un delegato:

Action<int> display = s => Console.WriteLine(s);

List<int> numbers =
[
    10,
    17
];
display(numbers.Count);   // output: 2

numbers.Clear();
display(numbers.Count);   // output: 0

Le parentesi si usano anche per richiamare un costruttore con l'operatore new.

Altri utilizzi di ()

È anche possibile usare le parentesi per specificare l'ordine in cui valutare le operazioni in un'espressione. Per altre informazioni, vedere Operatori C#.

Anche le espressioni cast, che eseguono conversioni dei tipi esplicite, usano le parentesi.

Indice dall'operatore end ^

Gli operatori di indice e intervallo possono essere usati con un tipo che può essere conteggiato. Un tipo conteggiabile è un tipo con una int proprietà denominata Count o Length con una funzione di accesso accessibile get . Le espressioni di raccolta si basano anche sui tipi conteggiabili .

L'operatore ^ indica la posizione dell'elemento dalla fine di una sequenza. Per una sequenza di lunghezza length, ^n punta all'elemento con offset length - n dall'inizio di una sequenza. Ad esempio, ^1 punta all'ultimo elemento di una sequenza e ^length punta al primo elemento di una sequenza.

int[] xs = [0, 10, 20, 30, 40];
int last = xs[^1];
Console.WriteLine(last);  // output: 40

List<string> lines = ["one", "two", "three", "four"];
string prelast = lines[^2];
Console.WriteLine(prelast);  // output: three

string word = "Twenty";
Index toFirst = ^word.Length;
char first = word[toFirst];
Console.WriteLine(first);  // output: T

Come illustrato nell'esempio precedente, l'espressione ^e è di tipo System.Index. Nell'espressione ^e, il risultato di e deve essere convertibile in modo implicito in int.

È anche possibile usare l'operatore ^ con l'operatore di intervallo per creare un intervallo di indici. Per altre informazioni, vedere Indici e intervalli.

A partire da C# 13, l'operatore Index dell'operatore end può essere usato in un inizializzatore di oggetto.

Operatore intervallo ..

L'operatore .. specifica l'inizio e la fine di un intervallo di indici come operandi. L'operando sinistro è un inizio inclusivo di un intervallo. L'operando destro è una fine esclusiva di un intervallo. Uno degli operandi può essere un indice dall'inizio o dalla fine di una sequenza, come illustrato nell'esempio seguente:

int[] numbers = [0, 10, 20, 30, 40, 50];
int start = 1;
int amountToTake = 3;
int[] subset = numbers[start..(start + amountToTake)];
Display(subset);  // output: 10 20 30

int margin = 1;
int[] inner = numbers[margin..^margin];
Display(inner);  // output: 10 20 30 40

string line = "one two three";
int amountToTakeFromEnd = 5;
Range endIndices = ^amountToTakeFromEnd..^0;
string end = line[endIndices];
Console.WriteLine(end);  // output: three

void Display<T>(IEnumerable<T> xs) => Console.WriteLine(string.Join(" ", xs));

Come illustrato nell'esempio precedente, l'espressione a..b è di tipo System.Range. Nell'espressione a..b, i risultati di a e b devono essere convertibili in modo implicito in Int32 o in Index.

Importante

Le conversioni implicite da int a Index generano un ArgumentOutOfRangeException quando il valore è negativo.

È possibile omettere uno degli operandi dell'operatore .. per ottenere un intervallo aperto:

  • a.. equivale a a..^0
  • ..b equivale a 0..b
  • .. equivale a 0..^0
int[] numbers = [0, 10, 20, 30, 40, 50];
int amountToDrop = numbers.Length / 2;

int[] rightHalf = numbers[amountToDrop..];
Display(rightHalf);  // output: 30 40 50

int[] leftHalf = numbers[..^amountToDrop];
Display(leftHalf);  // output: 0 10 20

int[] all = numbers[..];
Display(all);  // output: 0 10 20 30 40 50

void Display<T>(IEnumerable<T> xs) => Console.WriteLine(string.Join(" ", xs));

La tabella seguente illustra vari modi per esprimere gli intervalli di raccolta:

Espressione dell'operatore intervallo Descrizione
.. Tutti i valori della raccolta.
..end Valori dall'inizio a end escluso.
start.. Valori compresi tra start incluso e la fine.
start..end Valori compresi tra start incluso e end escluso.
^start.. Valori da start incluso al conteggio finale dalla fine.
..^end Valori dall'inizio a end escluso conteggiando dalla fine.
start..^end Valori da start incluso a end escluso conteggiando dalla fine.
^start..^end Valori da start incluso a end escluso conteggiando entrambi dalla fine.

Nell'esempio seguente viene illustrato l'effetto dell'utilizzo di tutti gli intervalli presentati nella tabella precedente:

int[] oneThroughTen =
[
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10
];

Write(oneThroughTen, ..);
Write(oneThroughTen, ..3);
Write(oneThroughTen, 2..);
Write(oneThroughTen, 3..5);
Write(oneThroughTen, ^2..);
Write(oneThroughTen, ..^3);
Write(oneThroughTen, 3..^4);
Write(oneThroughTen, ^4..^2);

static void Write(int[] values, Range range) =>
    Console.WriteLine($"{range}:\t{string.Join(", ", values[range])}");
// Sample output:
//      0..^0:      1, 2, 3, 4, 5, 6, 7, 8, 9, 10
//      0..3:       1, 2, 3
//      2..^0:      3, 4, 5, 6, 7, 8, 9, 10
//      3..5:       4, 5
//      ^2..^0:     9, 10
//      0..^3:      1, 2, 3, 4, 5, 6, 7
//      3..^4:      4, 5, 6
//      ^4..^2:     7, 8

Per altre informazioni, vedere Indici e intervalli.

Il token .. viene usato anche come operatore spread in un'espressione di raccolta.

Overload degli operatori

Gli operatori ., (), ^ e .. non possono essere sottoposti a overload. Anche l'operatore [] viene considerato un operatore che non supporta l'overload. Per il supporto dell'indicizzazione con tipi definiti dall'utente, usare gli indicizzatori.

Specifiche del linguaggio C#

Per altre informazioni, vedere le sezioni seguenti delle specifiche del linguaggio C#:

Per altre informazioni sugli indici e intervalli, vedere la nota sulla proposta di funzionalità.

Vedi anche