Операторы доступа к членам и выражения — операторы точки, индексатора и вызова.
Для доступа к члену типа используется несколько операторов и выражений. К этим операторам относятся доступ к члену (.
), элемент массива или доступ индексатора ([]
), индекс от конца (^
), диапазон (..
), операторы null-условные (?.
и) и ?[]
вызов метода (()
). К ним относятся операторы доступа к члену с значением NULL и индексатор (?[]
).?.
.
(доступ к члену) : для доступа к члену пространства имен или типа;[]
(элемент массива или индексатор доступа) : для доступа к элементу массива или индексатору типа;?.
и?[]
(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
Можно также использовать .
для вызова метода расширения.
Оператор индексатора []
Квадратные скобки, []
, обычно используются для доступа к элементам массива, индексатора или указателя. Начиная с C# 12, []
заключает выражение коллекции.
Доступ к массиву
В приведенном ниже примере показано, как получить доступ к элементам массива.
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]
вызовут то же исключение для отличного от NULLa
. Например, еслиa
является экземпляром массива, не равным null, иx
находится вне границa
,a?[x]
вызовет IndexOutOfRangeException.
Операторы с условием NULL предусматривают сокращенную обработку. То есть, если в цепочке операций условного доступа к элементу или члену одна из операций возвращает значение null
, остальная цепочка не выполняется. В следующем примере не вычисляется, если вычисляется и C
не оценивается, если A
или B
оцениваетсяnull
: B
A
null
A?.B?.Do(C);
A?.B?[C];
Если значение равно null, C
но не будет равно null, если A
значение A не равно NULL, необходимо применить только оператор с условным значением NULL кA
B
:
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.
Внимание
Неявные преобразования, отбрасываемые в int
Index
случае отрицательного 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
См. сведения в руководстве по диапазонам и индексам.
Маркер ..
также используется для элемента spread в выражении коллекции.
Возможность перегрузки оператора
Операторы .
, ^
()
и ..
операторы не могут быть перегружены. Оператор []
также считается неперегружаемым. Используйте индексаторы для поддержки индексирования с помощью определяемых пользователем типов.
Спецификация языка C#
Дополнительные сведения см. в следующих разделах статьи Спецификация языка C#:
См. сведения о индексах и диапазонах в примечании к функциям.