Операторы доступа к членам и выражения — операторы точки, индексатора и вызова.

Для доступа к члену типа используется несколько операторов и выражений. К этим операторам относятся доступ к члену (.), элемент массива или доступ индексатора ([]), индекс от конца (^), диапазон (..), операторы null-условные (?. и) и ?[]вызов метода (()). К ним относятся операторы доступа к члену с значением NULL и индексатор (?[]).?.

Выражение доступа к членам .

Маркер . используется для обращения к члену пространства имен или типа, как в следующих примерах.

  • Используйте . для обращения к пространству имен, вложенному в другое пространство имен, как показано в следующем примере директивы using.
using System.Collections.Generic;
  • Используйте . для создания полного имени для обращения к типу в пределах пространства имен, как показано в следующем коде:
System.Collections.Generic.IEnumerable<int> numbers = [1, 2, 3];

Используйте директиву using, чтобы сделать использование полных имен необязательным.

  • Используйте . для обращения к членам типов (статическим и нестатическим), как показано в следующем коде:
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

Можно также использовать . для вызова метода расширения.

Оператор индексатора []

Квадратные скобки, [], обычно используются для доступа к элементам массива, индексатора или указателя.

Доступ к массиву

В приведенном ниже примере показано, как получить доступ к элементам массива.

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

Если индекс массива выходит за границы соответствующего измерения массива, возникает исключение IndexOutOfRangeException.

Как показано в предыдущем примере, квадратные скобки также используются в объявлении типа массива и для создания экземпляров массива.

Дополнительные сведения см. в руководстве по работе с массивами.

Доступ к индексатору

В приведенном ниже примере используется тип .NET Dictionary<TKey,TValue> для получения доступа к индексатору:

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

Индексаторы позволяют индексировать экземпляры определяемого пользователем типа аналогично индексации массива. В отличие от индексов массива, которые должны быть целым числом, параметры индексатора могут быть объявлены любым типом.

Дополнительные сведения см. в руководстве по работе с индексаторами.

Другие данные об использовании []

Сведения о доступе к элементу указателя см. в разделе, посвященном оператору доступа к элементу указателя [], статьи Операторы, связанные с указателем.

Кроме того, с помощью квадратных скобок можно указывать атрибуты.

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

Операторы с условным ?. значением NULL и ?[]

Оператор с условным значением NULL применяет к операнду операцию доступа к элементу (?.) или доступ к элементу только?[] в том случае, если этот операнд оценивается как ненулевое; в противном случае возвращаетсяnull. Это означает следующее.

  • Если a вычисляется как null, то результатом a?.x или a?[x] является null.

  • Если a принимает значение, отличное от NULL, результат a?.x или a?[x] совпадает с результатом a.x или a[x]соответственно.

    Примечание.

    Если a.x или a[x] вызывает исключение, a?.x или a?[x] вызовут то же исключение для отличного от NULL a. Например, если a является экземпляром массива, не равным null, и x находится вне границ a, a?[x] вызовет IndexOutOfRangeException.

Операторы с условием NULL предусматривают сокращенную обработку. То есть, если в цепочке операций условного доступа к элементу или члену одна из операций возвращает значение null, остальная цепочка не выполняется. В следующем примере не вычисляется, если вычисляется и C не оценивается, если A или B оцениваетсяnull: BAnull

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

Если значение равно null, C но не будет равно null, если A значение A не равно NULL, необходимо применить только оператор с условным значением NULL кAB:

A?.B.C();

В предыдущем примере B не вычисляется и C() не вызывается, если A значение NULL. Однако при прерывании доступа к связанному члену, например, в круглых скобках, как в (A?.B).C(), не происходит сокращенное вычисление.

В следующем примере иллюстрируется использование операторов ?. и ?[]:

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

В первом из двух приведенных выше примеров также используется оператор объединения со значением NULL ??, что позволяет указать альтернативное выражение для вычисления в случае, если результат выполнения условной операции NULL — это null.

Если a.x или a[x] имеет тип Tзначения, не допускающего значение NULL, a?.x или a?[x] имеет соответствующий типT? значений, допускающий значение NULL. Если требуется выражение типа T, примените оператор объединения со значением NULL ?? к условному выражению NULL, как показано в следующем примере:

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

Если в предыдущем примере оператор ?? не используется, numbers?.Length < 2 вычисляется как false, если numbers имеет значение null.

Примечание.

Оператор ?. вычисляет левый операнд не более одного раза, гарантируя, что его нельзя изменить на null после того, как он пройдет проверку как не имеющий значение NULL.

Null-условный оператор доступа к элементу ?. также называется элвис-оператором.

Потокобезопасный вызов делегата

Используйте оператор ?. для проверки того, что делегат не равен null, и его вызова потокобезопасным способом (например, в том случае, когда вы собираетесь породить событие), как показано в следующем коде:

PropertyChanged?.Invoke(…)

Этот код эквивалентен следующему коду:

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

Предыдущий пример — это потокобезопасный способ, чтобы убедиться, что вызывается только ненулевое handler значение. Так как экземпляры делегата являются неизменяемыми, ни один из потоков не может изменить объект, на который ссылается локальная переменная handler. В частности, если код, выполняемый другим потоком, отменяет подписку на событие PropertyChanged и событие PropertyChanged принимает значение null до вызова handler, объект, на который ссылается handler, остается неизменным.

Выражения вызова ()

Используйте скобки, (), чтобы вызвать метод или делегат.

Приведенный ниже пример демонстрирует вызов делегата и метода с аргументами или без них.

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

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

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

Круглые скобки также можно использовать при вызове конструктора с оператором new.

Другие данные об использовании ()

Кроме того, с помощью круглых скобок можно настраивать порядок выполнения операций в выражении. Дополнительные сведения см. в разделе Операторы C#.

В выражениях приведения, которые выполняют явные преобразования типов, также используйте круглые скобки.

Индекс от конца: оператор ^

Операторы индекса и диапазона можно использовать с типом, подсчитываемым. Подсчитываемый тип — это тип, имеющий int свойство с именем Count или Length с доступным get методом доступа. Выражения коллекции также зависят от числовых типов.

Оператор ^ указывает позицию элемента из конца последовательности. Для последовательности длины length^n указывает на элемент с length - n смещения от начала последовательности. Например, ^1 указывает на последний элемент последовательности, а ^length — на первый элемент последовательности.

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

В предыдущем примере выражение ^e имеет тип System.Index. В выражении ^e результат e должен быть неявно преобразован в int.

Можно также использовать оператор ^ с оператором диапазона для создания диапазона индексов. См. сведения в руководстве по диапазонам и индексам.

Начиная с C# 13 индекс из конечного оператора можно использовать в инициализаторе объектов.

Оператор range ..

Оператор .. задает начало и конец диапазона индексов в качестве операндов. Левый операнд является инклюзивным началом диапазона. Правый операнд является эксклюзивным концом диапазона. Любой из операндов может быть индексом из начала или с конца последовательности, как показано в следующем примере:

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

В предыдущем примере выражение a..b имеет тип System.Range. В выражении a..b результаты a и b должны быть неявно преобразованы в Int32 или Index.

Внимание

Неявные преобразования, отбрасываемые в intIndex случае отрицательного ArgumentOutOfRangeException значения.

Можно проигнорировать любой из операндов оператора .., чтобы получить открытый диапазон:

  • a.. — это эквивалент a..^0
  • ..b — это эквивалент 0..b
  • .. — это эквивалент 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));

В следующей таблице показаны различные способы выражения диапазонов коллекций:

Выражение оператора диапазона Description
.. Все значения в коллекции.
..end Значения от начала до исключительного end .
start.. Значения от start инклюзивного до конца.
start..end Значения от start инклюзивного до исключительного end .
^start.. Значения от start инклюзивного до конца подсчета с конца.
..^end Значения от начала до исключительного end подсчета с конца.
start..^end Значения от start инклюзивного до end исключительно подсчета с конца.
^start..^end Значения от start инклюзивного до end исключительно подсчета от конца.

В следующем примере показано, как использовать все диапазоны, представленные в предыдущей таблице:

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

См. сведения в руководстве по диапазонам и индексам.

Маркер .. также используется в качестве оператора распространения в выражении коллекции.

Возможность перегрузки оператора

Операторы ., ^()и .. операторы не могут быть перегружены. Оператор [] также считается неперегружаемым. Используйте индексаторы для поддержки индексирования с помощью определяемых пользователем типов.

Спецификация языка C#

Дополнительные сведения см. в следующих разделах статьи Спецификация языка C#:

См. сведения о индексах и диапазонах в примечании к функциям.

См. также