Operatory i wyrażenia dostępu do składowych — operatory kropki, indeksatora i wywołania.

Aby uzyskać dostęp do elementu członkowskiego typu, należy użyć kilku operatorów i wyrażeń. Te operatory obejmują dostęp do składowych (.), element tablicy lub dostęp indeksatora ([]), indeks-from-end (), zakres (^..), operatory warunkowe o wartości null (?. i ?[]) oraz wywołanie metody (()). Należą do nich operatory dostępu warunkowego o wartości null (?.) i dostępu indeksatora (?[]).

Wyrażenie dostępu do składowych .

Token służy do uzyskiwania . dostępu do elementu członkowskiego przestrzeni nazw lub typu, jak pokazano w poniższych przykładach:

  • Użyj polecenia . , aby uzyskać dostęp do zagnieżdżonej przestrzeni nazw w przestrzeni nazw, jak pokazano w poniższym przykładzie using dyrektywy :
using System.Collections.Generic;
  • Użyj . polecenia , aby utworzyć kwalifikowaną nazwę , aby uzyskać dostęp do typu w przestrzeni nazw, jak pokazano w poniższym kodzie:
System.Collections.Generic.IEnumerable<int> numbers = [1, 2, 3];

using Użyj dyrektywy , aby korzystać z nazw kwalifikowanych jako opcjonalne.

  • Użyj . polecenia , aby uzyskać dostęp do elementów członkowskich typu, statycznych i niestacjonanych, jak pokazano w poniższym kodzie:
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

Możesz również użyć . metody , aby uzyskać dostęp do metody rozszerzenia.

Operator indeksatora []

Nawiasy kwadratowe, [], są zwykle używane do dostępu do tablicy, indeksatora lub wskaźnika.

Dostęp do tablicy

W poniższym przykładzie pokazano, jak uzyskać dostęp do elementów tablicy:

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

Jeśli indeks tablicy znajduje się poza granicami odpowiadającego wymiaru tablicy, IndexOutOfRangeException jest zwracany element .

Jak pokazano w poprzednim przykładzie, używane są również nawiasy kwadratowe podczas deklarowania typu tablicy lub tworzenia wystąpienia wystąpienia tablicy.

Aby uzyskać więcej informacji na temat tablic, zobacz Tablice.

Dostęp indeksatora

W poniższym przykładzie użyto typu .NET Dictionary<TKey,TValue> , aby zademonstrować dostęp indeksatora:

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

Indeksatory umożliwiają indeksowanie wystąpień typu zdefiniowanego przez użytkownika w podobny sposób jak indeksowanie tablicy. W przeciwieństwie do indeksów tablicowych, które muszą być liczbą całkowitą, parametry indeksatora można zadeklarować jako dowolny typ.

Aby uzyskać więcej informacji na temat indeksatorów, zobacz Indeksatory.

Inne użycie []

Aby uzyskać informacje o dostępie do elementu wskaźnika, zobacz sekcję Operator dostępu do elementu wskaźnika [] artykułu Operatory powiązane ze wskaźnikiem.

Można również użyć nawiasów kwadratowych, aby określić atrybuty:

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

Operatory warunkowe ?. o wartości null i ?[]

Operator warunkowy o wartości null stosuje operację dostępu do składowej (?.) lub dostępu do elementu (?[]) do operandu tylko wtedy, gdy operand zwróci wartość inną niż null; w przeciwnym razie zwraca wartość null. To znaczy:

  • Jeśli a wartość zostanie obliczona na nullwartość , wynik parametru a?.x lub a?[x] ma wartość null.

  • Jeśli a wartość ma wartość inną niż null, wynik a?.x lub a?[x] jest taki sam jak wynik a.x lub a[x], odpowiednio.

    Uwaga

    Jeśli a.x lub a[x] zgłosi wyjątek lub a?.xa?[x] zgłosi ten sam wyjątek dla wartości innej niż null a. Jeśli na przykład a jest wystąpieniem tablicy o wartości innej niż null i x znajduje się poza granicami a, a?[x] zgłasza błąd IndexOutOfRangeException.

Operatory warunkowe o wartości null są zwarciem. Oznacza to, że jeśli jedna operacja w łańcuchu operacji dostępu warunkowego lub elementu elementu zwraca nullwartość , pozostała część łańcucha nie jest wykonywana. W poniższym przykładzie nie jest obliczana, B jeśli A ocenia wartość null i C nie jest oceniana, czy lub BA ocenia nullwartość :

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

Jeśli A wartość może mieć wartość null, C ale B nie będzie mieć wartości null, jeśli wartość A nie ma wartości null, wystarczy zastosować operator warunkowy o wartości null do Apolecenia :

A?.B.C();

W poprzednim przykładzie B nie jest obliczany i C() nie jest wywoływany, jeśli A ma wartość null. Jeśli jednak dostęp do łańcuchowego elementu członkowskiego zostanie przerwany, na przykład przez nawiasy, tak jak w przypadku (A?.B).C(), zwarcie nie nastąpi.

W poniższych przykładach pokazano użycie operatorów ?. i ?[] :

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

Pierwszy z powyższych dwóch przykładów używa również operatora ?? łączenia wartości null, aby określić alternatywne wyrażenie do oceny w przypadku, gdy wynikiem operacji warunkowej null jest null.

Jeśli a.x lub a[x] ma typ Twartości innej niż null, a?.x lub a?[x] jest odpowiadającym mu typemT? wartości dopuszczanej do wartości null. Jeśli potrzebujesz wyrażenia typu T, zastosuj operator ?? łączenia wartości null do wyrażenia warunkowego o wartości null, jak pokazano w poniższym przykładzie:

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

W poprzednim przykładzie, jeśli nie używasz ?? operatora, numbers?.Length < 2 ocenia, false kiedy numbers ma wartość null.

Uwaga

Operator ?. ocenia operand po lewej stronie nie więcej niż raz, gwarantując, że nie można go zmienić na null po zweryfikowaniu jako nie null.

Operator ?. dostępu warunkowego o wartości null jest również znany jako operator Elvis.

Wywołanie delegata bezpiecznego wątkowo

?. Użyj operatora , aby sprawdzić, czy delegat nie ma wartości null i wywołać go w bezpieczny wątkowo sposób (na przykład podczas zgłaszania zdarzenia), jak pokazano w poniższym kodzie:

PropertyChanged?.Invoke(…)

Ten kod jest odpowiednikiem następującego kodu:

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

Powyższy przykład to bezpieczny wątkowo sposób, aby upewnić się, że wywoływana jest tylko wartość inną niż null handler . Ponieważ wystąpienia delegatów są niezmienne, żaden wątek nie może zmienić obiektu przywoływanego przez zmienną lokalną handler . W szczególności, jeśli kod wykonywany przez inny wątek anuluje PropertyChangednull subskrypcję zdarzenia i PropertyChanged stanie się przed handler wywołaniem, obiekt, handler do którego odwołuje się obiekt, pozostaje bez wpływu.

Wyrażenie wywołania ()

Użyj nawiasów, (), aby wywołać metodę lub wywołać delegata.

W poniższym przykładzie pokazano, jak wywołać metodę z argumentami lub bez argumentów i wywołać delegata:

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

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

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

Nawiasy są również używane podczas wywoływania konstruktora z operatorem new .

Inne zastosowania ()

Nawiasy służą również do dostosowywania kolejności oceniania operacji w wyrażeniu. Aby uzyskać więcej informacji, zobacz Operatory języka C#.

Wyrażenia rzutowe, które wykonują jawne konwersje typów, również używają nawiasów.

Indeksowanie z operatora końcowego ^

Operatory indeksu i zakresu mogą być używane z typem, który można liczyć. Typ zliczalny to typ, który ma int właściwość o nazwie Count lub Length z dostępnym get akcesorem. Wyrażenia kolekcji bazują również na typach zliczalnych.

Operator ^ wskazuje położenie elementu z końca sekwencji. W przypadku sekwencji długości length^n wskazuje element z przesunięciem length - n od początku sekwencji. Na przykład ^1 wskazuje ostatni element sekwencji i ^length wskazuje pierwszy element sekwencji.

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

Jak pokazano w poprzednim przykładzie, wyrażenie ^e jest System.Index typu . W wyrażeniu ^ee wynik musi być niejawnie konwertowany na int.

Możesz również użyć ^ operatora z operatorem zakresu, aby utworzyć zakres indeksów. Aby uzyskać więcej informacji, zobacz Indeksy i zakresy.

Począwszy od języka C# 13, indeks z operatora końcowego może być używany w inicjatorze obiektów.

Operator zakresu ..

Operator .. określa początek i koniec zakresu indeksów jako operandy. Operand po lewej stronie jest inkluzywnym początkiem zakresu. Operand po prawej stronie to ekskluzywny koniec zakresu. Jeden z operandów może być indeksem od początku lub od końca sekwencji, jak pokazano w poniższym przykładzie:

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

Jak pokazano w poprzednim przykładzie, wyrażenie a..b jest System.Range typu . W wyrażeniu a..ba wyniki i b muszą być niejawnie konwertowane na Int32 lub Index.

Ważne

Niejawne konwersje z int , aby zgłosić IndexArgumentOutOfRangeException wartość, gdy wartość jest ujemna.

Aby uzyskać zakres otwarty, można pominąć dowolny z operandów .. operatora:

  • a.. jest odpowiednikiem a..^0
  • ..b jest odpowiednikiem 0..b
  • .. jest odpowiednikiem 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));

W poniższej tabeli przedstawiono różne sposoby wyrażania zakresów kolekcji:

Wyrażenie operatora zakresu opis
.. Wszystkie wartości w kolekcji.
..end Wartości od początku do end wyłącznie.
start.. Wartości z start inkluzywnie do końca.
start..end Wartości z start inkluzywnie do end wyłącznie.
^start.. Wartości z start inkluzywnie do końca liczone od końca.
..^end Wartości od początku do end wyłącznie liczone od końca.
start..^end Wartości z start inkluzywnego do end wyłącznie zliczania od końca.
^start..^end Wartości z start inkluzywnego do end wyłącznie zliczania od końca.

W poniższym przykładzie pokazano efekt użycia wszystkich zakresów przedstawionych w poprzedniej tabeli:

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

Aby uzyskać więcej informacji, zobacz Indeksy i zakresy.

Token .. jest również używany jako operator spreadu w wyrażeniu kolekcji.

Przeciążenie operatora

Operatory ., (), ^i .. nie mogą być przeciążone. Operator [] jest również uważany za operator nienaładowalny. Indeksatory umożliwiają obsługę indeksowania z typami zdefiniowanymi przez użytkownika.

specyfikacja języka C#

Aby uzyskać więcej informacji, zobacz następujące sekcje specyfikacji języka C#:

Aby uzyskać więcej informacji na temat indeksów i zakresów, zobacz notatkę dotyczącą propozycji funkcji.

Zobacz też