Операторы и выражения для доступа к элементам (справочник по C#)

При получении доступа к элементу типа можно использовать следующие операторы и выражения:

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

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

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

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

  • Используйте . для обращения к членам типов (статическим и нестатическим), как показано в следующем коде:
var constants = new List<double>();
constants.Add(Math.PI);
constants.Add(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-условные операторы: ?. и ?[]

В C# 6 и более поздних версий доступен оператор с условием 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, остальная цепочка не выполняется. В следующем примере B не вычисляется, если A принимает значение null, и C не вычисляется, если A или B принимает значение null.

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

Если параметр A может быть пустым, а B и C нет, если A не является пустым, необходимо только применить условный оператор NULL к параметру A:

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

var numberSets = new List<double[]>
{
    new[] { 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
using System;
using System.Collections.Generic;
using System.Linq;

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 FullName Name { get; set; }
    }

    public class FullName
    {
        public string FirstName { get; set; }
        public 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(new int[0]));  // output: 0
Console.WriteLine(GetSumOfFirstTwoOrDefault(new[] { 3, 4, 5 }));  // output: 7

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

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

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

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

PropertyChanged?.Invoke(…)

Этот код эквивалентен следующему коду, который будет использоваться в C# 5 или более ранней версии:

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

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

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

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

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

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

var numbers = new List<int>();
numbers.Add(10);
numbers.Add(17);
display(numbers.Count);   // output: 2

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

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

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

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

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

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

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

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

var lines = new List<string> { "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# 8.0 и последующих версиях, определяет начало и конец диапазона индексов в качестве своих операндов. Левый операнд является инклюзивным началом диапазона. Правый операнд является эксклюзивным концом диапазона. Любой из операндов может быть индексом от начала или конца последовательности, как показано в следующем примере:

int[] numbers = new[] { 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 должны быть неявно преобразованы в int или Index.

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

  • a.. — это эквивалент a..^0
  • ..b — это эквивалент 0..b
  • .. — это эквивалент 0..^0
int[] numbers = new[] { 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));

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

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

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

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

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

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

См. также