Partager via


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. Les opérateurs d'accès aux membres incluent l'accès aux membres (.), l'élément de tableau ou l'accès à l'indexeur ([]), l'index depuis la fin (^), la plage (..), les opérateurs conditionnels de nullité (?. et ?[]), et l'appel de méthode (()). Ces opérateurs incluent les opérateurs d’accès de membre conditionnel null (?.) et d’accès à l’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. À compter de C# 12, [] place une expression de collection.

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. Pour plus d’informations sur les expressions de collection, consultez l’article sur les expressions de collection.

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

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

En outre, les crochets peuvent être utilisés pour désigner des modèles de liste à utiliser dans les critères de correspondance ou de test.

arr is ([1, 2, ..])
//Specifies that an array starts with (1, 2)

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. En d’autres termes :

  • 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 exemple précédent utilise également l’opérateur de coalescence null ?? pour spécifier une autre expression à évaluer si le résultat d’une opération conditionnelle null est null.

Si a.x ou a[x] est un type de valeur ne pouvant pas accepter la valeur Null T, a?.x ou a?[x] est le type de valeur pouvant accepter la valeur Null correspondant T?. 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 seule fois, garantissant qu’il ne peut pas être modifié en null après vérification de sa non-nullité.

À compter de C# 14, l’affectation est autorisée avec une expression d’accès conditionnel Null (?. et ?[]) sur les types de référence. Par exemple, consultez la méthode suivante :

person?.FirstName = "Scott";
messages?[5] = "five";

L’exemple précédent montre l’affectation à une propriété et à un élément indexé d’un type de référence qui peut être nul. Un comportement important pour cette attribution est que l'expression sur la partie droite du = est évaluée uniquement lorsque la partie gauche est connue pour être non nulle. Par exemple, dans le code suivant, la fonction GenerateNextIndex est appelée uniquement lorsque le values tableau n’est pas null. Si le values tableau est null, GenerateNextIndex n’est pas appelé :

person?.FirstName = "Scott";
messages?[5] = "five";

En d’autres termes, le code précédent équivaut au code suivant à l’aide d’une if instruction pour la vérification null :

if (values is not null)
{
    values[2] = GenerateNextIndex();
}

En plus de l’affectation, toute forme d’affectation composée, telle que += ou -=, est autorisée. Toutefois, l’incrément (++) et la décrémentation (--) ne sont pas autorisés.

Cette amélioration ne classifie pas une expression conditionnelle Null en tant que variable. Elle ne peut pas être ref affectée, ni affectée à une ref variable, ni passée à une méthode en tant que ref ou out argument.

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é.

Le code suivant montre comment appeler une méthode, avec ou sans arguments, et appeler 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 ^

Les opérateurs d’index et de plage peuvent être utilisés avec un type, à savoir comptable. Le type comptable dispose d’une propriété int nommée Count ou Length avec un accesseur get accessible. Les expressions de collection s’appuient également sur les types comptable.

Notes

Les tableaux unidimensionnels sont dénombrables. Les tableaux multidimensionnels ne le sont pas. Les opérateurs ^ et .. (plage) ne peuvent pas être utilisés dans des tableaux multidimensionnels.

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.

À compter de C# 13, l’index de l’opérateur final peut être utilisé dans un initialiseur d’objet.

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 ^e est de type System.Index. 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
  • a.. équivaut à a..^0
  • a.. équivaut à a..^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 Descriptif
.. 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é pour l’élément 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