Udostępnij za pośrednictwem


Operatory i wyrażenia dotyczące dostępu do członków — 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ń. Operatory dostępu do składowych obejmują dostęp do składowych (.), element tablicy lub dostęp indeksatora ([]), indeks od końca (^), zakres (..), operatory warunkowe wartości null (?. i ?[]) oraz wywołanie metody (()). Te operatory obejmują operatory dostępu warunkowego o wartości null (?.) oraz operatory dostępu indeksatora (?[]).

Wyrażenie dostępu do składowych .

Używasz tokenu . do uzyskiwania dostępu do członka przestrzeni nazw lub typu, co pokazują poniższe przykłady:

  • Użyj polecenia . do uzyskania dostępu do zagnieżdżonej przestrzeni nazw w ramach przestrzeni nazw, jak pokazano na poniższym przykładzie dyrektywy using:
using System.Collections.Generic;
  • Użyj . do utworzenia kwalifikowanej nazwy, aby uzyskać dostęp do typu w przestrzeni nazw, jak pokazuje poniższy kod:
System.Collections.Generic.IEnumerable<int> numbers = [1, 2, 3];

Użyj dyrektywy using, aby opcjonalnie używać nazw kwalifikowanych.

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ć . do uzyskania dostępu do metody rozszerzenia .

Operator indeksatora []

Nawiasy kwadratowe, [], są zwykle używane do uzyskiwania dostępu do tablicy, indeksatora lub elementu wskazywanego wskaźnikiem. Począwszy od języka C# 12, [] otacza wyrażenie kolekcji.

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, rzucany jest IndexOutOfRangeException.

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

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życia []

Aby uzyskać informacje o dostępie do elementu wskaźnika, zobacz sekcję Operator dostępu do elementu wskaźnika [] w artykule Operatory związane ze wskaźnikami. Aby uzyskać informacje na temat wyrażeń kolekcji, zapoznaj się z artykułem o wyrażeniach kolekcji.

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

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

Ponadto nawiasy kwadratowe mogą służyć do wyznaczania wzorców listy do użycia w dopasowywaniu lub testowaniu wzorców.

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

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

Operator warunkowo-nullowy stosuje operację dostępu do składowej () lub dostępu do elementu (?.) do operandu tylko wtedy, gdy operand jest nienullowy; w przeciwnym razie zwraca . Innymi słowy:

  • Jeśli a zostanie przekształcone na null, wynik a?.x lub a?[x] to null.

  • Jeśli a 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] rzuci wyjątek, to a?.x lub a?[x] rzuci ten sam wyjątek w przypadku, gdy a nie jest null. Na przykład, jeśli a jest wystąpieniem tablicy o wartości innej niż null, a x znajduje się poza granicami a, a?[x] zgłosi IndexOutOfRangeException.

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

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

Jeśli może mieć wartość null, ale i nie będą mieć wartości null, jeśli nie ma wartości null, wystarczy zastosować operator warunkowy dla wartości null do .

A?.B.C();

W poprzednim przykładzie B nie jest ewaluowany i C() nie jest wywoływany, jeśli A ma wartość null. Jeśli jednak łańcuchowy dostęp do elementów zostanie przerwany, na przykład przez nawiasy, tak jak w przypadku (A?.B).C(), mechanizm krótkiego spięcia nie zostanie wykorzystany.

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

W poprzednim przykładzie użyto również operatora łączenia dla 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] jest typu wartości nie dopuszczającej do wartości null T, a?.x lub a?[x] ma odpowiadający typ wartości dopuszczającej do wartości null T?. Jeśli potrzebujesz wyrażenia typu T, zastosuj operator ?? do wyrażenia warunkowego z 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 przyjmuje wartość false kiedy numbers jest null.

Uwaga

Operator ?. ocenia swój lewostronny operand nie więcej niż raz, gwarantując, że nie można go zmienić na null po zweryfikowaniu jako niebędący null.

Począwszy od języka C# 14, przypisanie jest dopuszczalne przy użyciu wyrażenia dostępu warunkowego o wartości null (?. i ?[]) w typach referencyjnych. Zobacz na przykład następującą metodę:

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

W poprzednim przykładzie pokazano przypisanie do właściwości i indeksowany element w typie odwołania, który może mieć wartość null. Ważnym zachowaniem tego przypisania jest to, że wyrażenie po prawej stronie = jest oceniane tylko wtedy, gdy wiadomo, że lewa strona nie jest pusta. Na przykład w poniższym kodzie funkcja GenerateNextIndex jest wywoływana tylko wtedy, gdy tablica values nie ma wartości null. Jeśli tablica values ma wartość null, GenerateNextIndex nie jest wywoływana:

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

Innymi słowy, poprzedni kod jest odpowiednikiem następującego kodu za pomocą instrukcji if do sprawdzenia wartości null.

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

Oprócz przypisania, dowolna forma przypisania złożonego, na przykład += lub -=, jest dozwolona. Jednak przyrost (++) i dekrementacja (--) nie są dozwolone.

To ulepszenie nie klasyfikuje wyrażenia warunkowego o wartości null jako zmiennej. Nie można go ref przypisać, nie można też przypisać do zmiennej ref ani przekazać do metody jako argument ref lub out.

Wywołanie bezpiecznego dla wątków delegata

?. Użyj tego operatora, aby sprawdzić, czy delegat nie jest wartością null i wywołać go w sposób bezpieczny dla wątków (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ływany jest tylko obiekt nie-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 wypisuje się z zdarzenia , a staje się przed wywołaniem , obiekt, do którego odwołuje się , pozostaje bez wpływu.

Wyrażenie wywołania ()

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

Poniższy kod demonstruje, jak wywołać metodę z 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

Podczas wywoływania konstruktora za pomocą new operatora również stosuje się nawiasy.

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 jest przeliczalny. Typ zliczalny to typ, który ma właściwość nazwaną albo int, albo Count z dostępnym akcesorem Length. Wyrażenia kolekcji bazują na typach zliczalnych również.

Uwaga

Tablice jednowymiarowe są zliczalne. Tablice wielowymiarowe nie są. Operatory ^ i .. (zakres) nie mogą być używane w tablicach wielowymiarowych.

Operator ^ wskazuje położenie elementu od końca sekwencji. W przypadku sekwencji o długości length, ^n wskazuje element z przesunięciem length - n od początku sekwencji. Na przykład ^1 wskazuje ostatni element sekwencji, a ^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 typu System.Index. W wyrażeniu ^e, wynik e musi być niejawnie konwertowalny na int.

Możesz również użyć operatora ^ razem 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 jego operandy. Operand po lewej stronie jest początkiem inkluzywnego zakresu. Operand po prawej stronie jest wyłącznym końcem 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 typu System.Range. W wyrażeniu a..b, wyniki a i b muszą być niejawnie konwertowane na Int32 lub Index.

Ważne

Niejawne konwersje z int do Index wyrzucają ArgumentOutOfRangeException gdy wartość jest ujemna.

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

  • jest odpowiednikiem
  • jest odpowiednikiem
  • jest odpowiednikiem
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 od start włącznie do końca.
start..end Wartości od start łącznie do end wyłącznie.
^start.. Wartości od start aż do końca, licząc od końca.
..^end Wartości od początku do end, liczone wyłącznie od końca.
start..^end Wartości od start, włącznie, do end, wyłącznie, licząc od końca.
^start..^end Wartości od start włącznie do end wyłącznie, licząc 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 dla elementu spread w wyrażeniu kolekcji.

Przeciążenie operatora

Operatory ., (), ^, i .. nie mogą być przeciążone. Operator [] jest również uważany za operator nieprzeciążalny. Użyj indeksatorów do obsługi 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ż