Opérateurs et expressions d’accès aux membres : opérateurs de point, d’indexeur et d’appel.

Vous utilisez plusieurs opérateurs et expressions pour accéder à un membre de type. Ces opérateurs incluent l’accès aux membres (.), l’élément de tableau ou l’accès à l’indexeur ([]), l’index de fin (^), la plage (..), les opérateurs conditionnels Null (?. et ?[]) et l’appel de méthode (()). Il s’agit notamment des opérateurs d’accès au membre conditionnel Null (?.) et d’indexeur (?[]).

Expression d'accès au membre .

Le jeton . sert à accéder à l’un des membres d’un espace de noms ou d’un type, comme le montrent les exemples suivants :

  • Utilisez . pour accéder à un espace de noms imbriqué dans un autre, comme le montre l’exemple suivant avec la directive using :
using System.Collections.Generic;
  • Utilisez . pour former un nom qualifié permettant d’accéder à un type dans un espace de noms, comme le montre le code suivant :
System.Collections.Generic.IEnumerable<int> numbers = [1, 2, 3];

Utilisez une directive using pour rendre facultative l’utilisation de noms qualifiés.

  • Utilisez . pour accéder aux membres de type, statiques et non statiques, comme le montre le code suivant :
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

Vous pouvez également utiliser . pour accéder à une méthode d’extension.

Opérateur d’indexeur []

Les crochets, [], sont généralement utilisés pour l’accès à un élément tableau, indexeur ou pointeur.

Accès aux tableaux

L’exemple suivant montre comment accéder à des éléments tableau :

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

Si un index de tableau est en dehors des limites de la dimension correspondante d’un tableau, une IndexOutOfRangeException est levée.

Comme le montre l’exemple précédent, vous utilisez également des crochets quand vous déclarez un type tableau ou instanciez une instance de tableau.

Pour plus d’informations sur les tableaux, consultez Tableaux.

Accès aux indexeurs

L’exemple suivant utilise le type .NET Dictionary<TKey,TValue> afin d’illustrer l’accès aux indexeurs :

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

Les indexeurs vous permettent d’indexer des instances d’un type défini par l’utilisateur en procédant de la même façon que pour l’indexation de tableau. Contrairement aux index de tableau, qui doivent être des entiers, les paramètres d’indexeur peuvent être déclarés comme étant de n’importe quel type.

Pour plus d’informations sur les indexeurs, consultez Indexeurs.

Autres utilisations de []

Pour plus d’informations concernant l’accès à l’élément de pointeur, consultez la section Opérateur d’accès à l’élément de pointeur [] de l’article Opérateurs associés au pointeur.

Vous utilisez également des crochets pour spécifier des attributs :

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

Opérateurs conditionnels Null ?. et ?[]

Un opérateur conditionnel Null n’applique une opération d'accès à un membre(?.) ou accès à un élément(?[]), à son opérande que si cet opérande a la valeur non Null ; sinon, il retourne la valeur null. Plus précisément :

  • Si a renvoie la valeur null, le résultat de a?.x ou a?[x] est null.

  • Si a renvoie la valeur non Null, le résultat de a?.x ou a?[x] est le même que le résultat de a.x ou a[x], respectivement.

    Notes

    Si a.x ou a[x] lève une exception, a?.x ou a?[x] lève la même exception pour la valeur non Null a. Par exemple, si a est une instance de tableau non Null et que x est en dehors des limites de a, a?[x] lève une IndexOutOfRangeException.

Les opérateurs conditionnels Null ont un effet de court-circuit. Autrement dit, si une opération dans une chaîne d’opérations d’accès au membre ou à l’élément conditionnelles retourne une valeur null, le reste de la chaîne ne s’exécute pas. Dans l’exemple suivant, B n’est pas évalué si A renvoie la valeur null et C n’est pas évalué si A ou B renvoie la valeur null :

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

Si A peut être Null, mais que B et C ne peuvent être Null si A n’est pas Null, vous devez uniquement appliquer l’opérateur conditionnel Null à A :

A?.B.C();

Dans l’exemple précédent, B n’est pas évalué et C() n’est pas appelé si A est Null. Toutefois, si l’accès au membre chaîné est interrompu, par exemple par des parenthèses comme dans (A?.B).C(), il n’y a pas d’effet de court-circuit.

Les exemples suivants illustrent l'utilisation des opérateurs ?. et ?[] :

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}");
}

Le premier des deux exemples précédents utilise également l’opérateur de coalescence nulle?? pour spécifier une autre expression à évaluer si le résultat d’une opération conditionnelle nulle est null.

Si a.x ou a[x] correspond à un type de valeur est d’un type de valeur pouvant accepter la valeur Null T, a?.x ou a?[x] est le type de valeur pouvant accepter la valeur Null correspondanteT?. Si vous avez besoin d’une expression de type T, appliquez l’opérateur de coalescence nulle ?? à une expression conditionnelle Null, comme l’illustre l’exemple suivant :

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

Dans l’exemple précédent, si vous n’utilisez pas l’opérateur ??, numbers?.Length < 2 renvoie la valeur false lorsque numbers est null.

Notes

L’opérateur ?. évalue son opérande de gauche au maximum une fois, ce qui garantit qu’il ne peut pas être remplacé par null après avoir été vérifié comme étant non Null.

L’opérateur d’accès aux membres conditionnels null ?. est également appelé l’opérateur Elvis.

Appel de délégué thread-safe

Utilisez l’opérateur ?. pour vérifier si un délégué est non Null et l’appeler de manière thread-safe (par exemple, quand vous déclenchez un événement), comme illustré dans le code suivant :

PropertyChanged?.Invoke(…)

Ce code équivaut au code suivant :

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

L’exemple précédent est un moyen thread-safe de s’assurer que seule une valeur non Null handler est appelée. Les instances de délégué étant immuables, aucun thread ne peut modifier l’objet référencé par la variable locale handler. Plus particulièrement, si le code exécuté par un autre thread se désabonne de l’événement PropertyChanged et que PropertyChanged devient null avant l’appel de handler, l’objet référencé par handler reste inchangé.

Expression d’appel ()

Utilisez des parenthèses, (), pour appeler une méthode ou un délégué.

L’exemple suivant montre comment appeler une méthode, avec ou sans arguments, et un délégué :

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

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

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

Vous utilisez également des parenthèses quand vous appelez un constructeur avec l’opérateur new.

Autres utilisations de ()

Vous utilisez également des parenthèses pour ajuster l’ordre dans lequel évaluer les opérations dans une expression. Pour plus d’informations, consultez Opérateurs C#.

Les expressions cast, qui effectuent des conversions de type explicites, utilisent aussi des parenthèses.

Index à partir de l’opérateur de fin ^

L’opérateur ^ indique la position de l’élément à partir de la fin d’une séquence. Pour une séquence de longueur length, ^n pointe vers l’élément avec un décalage length - n à partir du début d’une séquence. Par exemple, ^1 pointe vers le dernier élément d’une séquence et ^length pointe vers le premier élément d’une séquence.

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

Comme l’illustre l’exemple précédent, l’expression ^e est de type System.Index. Dans l’expression ^e, le résultat de e doit être implicitement convertible en int.

Vous pouvez également utiliser l’opérateur ^ avec l’opérateur de plage pour créer une plage d’index. Pour plus d’informations, consultez Index et plages.

Opérateur de plage ..

L’opérateur .. spécifie le début et la fin d’une plage d’index en tant qu’opérandes. L’opérande de gauche correspond à un début inclusif de plage. L’opérande de droite correspond à une fin exclusive de plage. L’un des opérandes peut être un index commençant à partir du début ou de la fin d’une séquence, comme le montre l’exemple suivant :

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));

Comme l’illustre l’exemple précédent, l’expression a..b est de type System.Range. Dans l’expression a..b, les résultats de a et b doivent être implicitement convertibles en Int32 ou Index.

Important

Les conversions implicites de int à Index lèvent ArgumentOutOfRangeException lorsque la valeur est négative.

Vous pouvez omettre l’un des opérandes de l’opérateur .. pour obtenir une plage ouverte :

  • a.. équivaut à a..^0
  • ..b équivaut à 0..b
  • .. équivaut à 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));

Le tableau suivant montre différentes façons d’exprimer des plages de collection :

Expression d’opérateur de plage Description
.. Toutes les valeurs de la collection.
..end Valeurs comprises du début à la end exclusivement.
start.. Valeurs comprises du start inclusivement à la fin.
start..end Valeurs comprises du start inclusivement à la end exclusivement.
^start.. Valeurs comprises du start inclusivement à la fin en comptant à partir de la fin.
..^end Valeurs comprises du début à la end exclusivement en comptant à partir de la fin.
start..^end Valeurs comprises du start inclusivement à la end exclusivement en comptant à partir de la fin.
^start..^end Valeurs comprises du start inclusivement à la end exclusivement en comptant à partir de la fin.

L’exemple suivant illustre l’effet de l’utilisation de toutes les plages présentées dans le tableau précédent :

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

Pour plus d’informations, consultez Index et plages.

Le jeton .. est également utilisé comme opérateur de propagation dans une expression de collection.

Capacité de surcharge de l’opérateur

Les opérateurs ., (), ^ et .. ne peuvent pas être surchargés. L’opérateur [] est également considéré comme un opérateur non surchargeable. Utilisez des indexeurs pour prendre en charge l’indexation avec des types définis par l’utilisateur.

spécification du langage C#

Pour plus d’informations, consultez les sections suivantes de la spécification du langage C# :

Pour plus d’informations sur les index et les plages, consultez la note de proposition de fonctionnalité.

Voir aussi