Partilhar via


Operadores de acesso de membro e expressões - os operadores de ponto, indexador e invocação.

Você usa vários operadores e expressões para acessar um membro do tipo. Esses operadores incluem acesso de membro (.), acesso de elemento de matriz ou indexador ([]), index-from-end (^), range (..), operadores condicionais nulos (?. e ?[]) e invocação de método (()). Estes incluem os operadores null-conditional member access (?.) e indexer access (?[]).

Expressão de acesso de membro .

Você usa o . token para acessar um membro de um namespace ou um tipo, como demonstram os exemplos a seguir:

  • Use . para acessar um namespace aninhado dentro de um namespace, como mostra o exemplo de diretiva using a seguir:
using System.Collections.Generic;
  • Use . para formar um nome qualificado para acessar um tipo dentro de um namespace, como mostra o código a seguir:
System.Collections.Generic.IEnumerable<int> numbers = [1, 2, 3];

Use uma using diretiva para tornar opcional o uso de nomes qualificados.

  • Use . para acessar membros do tipo, estáticos e não estáticos, como mostra o código a seguir:
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

Você também pode usar . para acessar um método de extensão.

Operador indexador []

Os colchetes, [], são normalmente usados para acesso a matrizes, indexadores ou elementos de ponteiro. Começando com C# 12, [] inclui uma expressão de coleção.

Acesso ao array

O exemplo a seguir demonstra como acessar elementos de matriz:

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

Se um índice de matriz estiver fora dos limites da dimensão correspondente de uma matriz, um IndexOutOfRangeException será lançado.

Como mostra o exemplo anterior, você também usa colchetes quando declara um tipo de matriz ou instancia uma instância de matriz.

Para obter mais informações sobre matrizes, consulte Matrizes.

Acesso ao indexador

O exemplo a seguir usa o tipo .NET Dictionary<TKey,TValue> para demonstrar o acesso do indexador:

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

Os indexadores permitem indexar instâncias de um tipo definido pelo usuário da mesma forma que a indexação de matrizes. Ao contrário dos índices de matriz, que devem ser inteiros, os parâmetros do indexador podem ser declarados como sendo de qualquer tipo.

Para obter mais informações sobre indexadores, consulte Indexadores.

Outros usos de []

Para obter informações sobre o acesso ao elemento de ponteiro, consulte a seção Operador de acesso ao elemento de ponteiro [] do artigo Operadores relacionados ao ponteiro. Para obter informações sobre expressões de coleção, consulte o artigo de expressões de coleção.

Você também usa colchetes para especificar atributos:

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

Operadores ?. condicionais nulos e ?[]

Um operador condicional nulo aplica uma operação de acesso de membro (?.) ou de acesso de elemento (?[]) ao seu operando somente se esse operando for avaliado como não-nulo, caso contrário, ele retornará null. Por outras palavras:

  • Se a avalia a null, o resultado de a?.x ou a?[x] é null.

  • Se a for avaliado como não-nulo, o resultado de a?.x ou a?[x] é o mesmo que o resultado de a.x ou a[x], respectivamente.

    Nota

    Se a.x ou a[x] lança uma exceção, a?.x ou a?[x] lançaria a mesma exceção para não-nulo a. Por exemplo, se a for uma instância de matriz não nula e x estiver fora dos limites de a, a?[x] lançaria um IndexOutOfRangeExceptionarquivo .

Os operadores condicionais nulos são curto-circuitos. Ou seja, se uma operação em uma cadeia de operações de acesso a membros ou elementos condicionais retornar null, o restante da cadeia não será executado. No exemplo a seguir, B não é avaliado se A avalia para null e C não é avaliado se A ou B avalia como null:

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

Se A pode ser null, mas B e C não seria null se A não for null, você só precisa aplicar o operador null-conditional a A:

A?.B.C();

No exemplo anterior, B não é avaliado e C() não é chamado se A for nulo. No entanto, se o acesso do membro encadeado for interrompido, por exemplo, entre parênteses como no (A?.B).C(), o curto-circuito não acontece.

Os exemplos a seguir demonstram o uso dos ?. operadores e ?[] :

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

O primeiro dos dois exemplos anteriores também usa o operador ?? null-coalescing para especificar uma expressão alternativa para avaliar caso o resultado de uma operação null-conditional seja null.

Se a.x ou a[x] é de um tipo Tde valor não anulável , a?.x ou a?[x] é do tipo T?de valor anulável correspondente . Se você precisar de uma expressão do tipo T, aplique o operador ?? null-coalescing a uma expressão null-condicional, como mostra o exemplo a seguir:

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

No exemplo anterior, se você não usar o ?? operador, numbers?.Length < 2 avalia quando false numbers é null.

Nota

O ?. operador avalia seu operando esquerdo no máximo uma vez, garantindo que ele não possa ser alterado para null depois de ser verificado como não nulo.

O operador ?. de acesso de membro nulo e condicional também é conhecido como operador Elvis.

Invocação de delegado thread-safe

Use o ?. operador para verificar se um delegado não é nulo e invoque-o de forma segura para threads (por exemplo, quando você gera um evento), como mostra o código a seguir:

PropertyChanged?.Invoke(…)

Esse código é equivalente ao seguinte código:

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

O exemplo anterior é uma maneira segura de threads para garantir que apenas um não-nulo handler seja invocado. Como as instâncias delegadas são imutáveis, nenhum thread pode alterar o objeto referenciado handler pela variável local. Em particular, se o código executado por outro thread cancelar a PropertyChanged inscrição do evento e PropertyChanged se tornar null antes handler é invocado, o objeto referenciado por handler permanece inalterado.

Expressão de invocação ()

Use parênteses, (), para chamar um método ou invocar um delegado.

O exemplo a seguir demonstra como chamar um método, com ou sem argumentos, e invocar um delegado:

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

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

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

Você também usa parênteses quando invoca um construtor com o new operador.

Outros usos de ()

Você também usa parênteses para ajustar a ordem na qual avaliar operações em uma expressão. Para obter mais informações, consulte Operadores C#.

As expressões de transmissão, que executam conversões de tipo explícitas, também usam parênteses.

Índice do operador final ^

Os operadores de índice e intervalo podem ser usados com um tipo que é contável. Um tipo contável é um tipo que tem uma int propriedade chamada ou Count Length com um acessador acessível get . As expressões de coleção também dependem de tipos contáveis .

O ^ operador indica a posição do elemento a partir do final de uma sequência. Para uma sequência de comprimento length, ^n aponta para o elemento com deslocamento length - n desde o início de uma sequência. Por exemplo, ^1 aponta para o último elemento de uma sequência e ^length aponta para o primeiro elemento de uma sequência.

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

Como mostra o exemplo anterior, a expressão ^e é do System.Index tipo. Em expressão ^e, o resultado de e deve ser implicitamente conversível em int.

Você também pode usar o ^ operador com o operador de intervalo para criar um intervalo de índices. Para obter mais informações, consulte Índices e intervalos.

A partir do C# 13, o índice do operador final pode ser usado em um inicializador de objeto.

Operador de gama ..

O .. operador especifica o início e o fim de um intervalo de índices como seus operandos. O operando esquerdo é um início inclusivo de um intervalo. O operando do lado direito é uma extremidade exclusiva de um intervalo. Qualquer um dos operandos pode ser um índice do início ou do final de uma sequência, como mostra o exemplo a seguir:

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

Como mostra o exemplo anterior, a expressão a..b é do System.Range tipo. Em expressão a..b, os resultados de a e b devem ser implicitamente convertíveis em Int32 ou Index.

Importante

Conversões implícitas de int para Index lançar um ArgumentOutOfRangeException quando o valor é negativo.

Você pode omitir qualquer um dos operandos do .. operador para obter um intervalo aberto:

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

A tabela a seguir mostra várias maneiras de expressar intervalos de coleção:

Expressão do operador de intervalo Description
.. Todos os valores na coleção.
..end Valores desde o início até o end exclusivo.
start.. Valores desde o start inclusivo até ao fim.
start..end Valores do start inclusivo ao end exclusivo.
^start.. Valores desde o start inclusivo até o final contando a partir do final.
..^end Valores desde o início até à end contagem exclusiva a partir do fim.
start..^end Valores de start inclusivo a end exclusivamente contando a partir do final.
^start..^end Valores de start inclusivos a end exclusivamente ambos contando a partir do final.

O exemplo a seguir demonstra o efeito do uso de todos os intervalos apresentados na tabela anterior:

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

Para obter mais informações, consulte Índices e intervalos.

O .. token também é usado para o elemento spread em uma expressão de coleção.

Capacidade de sobrecarga do operador

Os .operadores , (), ^e .. não podem ser sobrecarregados. O [] operador também é considerado um operador não sobrecarregado. Use indexadores para dar suporte à indexação com tipos definidos pelo usuário.

Especificação da linguagem C#

Para obter mais informações, consulte as seguintes seções da especificação da linguagem C#:

Para obter mais informações sobre índices e intervalos, consulte a nota de proposta de recurso.

Consulte também