Partilhar via


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

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

Expressão de acesso a membro .

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

  • Use . para aceder a um namespace aninhado dentro de outro namespace, como mostra o seguinte exemplo de uma diretiva using using:
using System.Collections.Generic;
  • Use . para formar um nome qualificado para aceder a um tipo dentro de um espaço de nomes, 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 aceder a membros do tipo, estáticos e não estáticos, como demonstra 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 acessar 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 operador de acesso ao elemento de ponteiro [], consulte a seção 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() {}

Além disso, colchetes podem ser usados para designar padrões de lista para uso em correspondência de padrões ou testes.

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

Operadores condicionais nulos ?. e ?[]

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

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

  • Se a é 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. Por exemplo, se a for uma instância de array não nula e x estiver fora dos limites de c2, a?[x] lançaria um IndexOutOfRangeException.

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

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

Se A puder ser null, mas B e C não seriam null se A não fosse null, só precisa aplicar o operador de condição nula 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 exemplo mencionado também utiliza o operador de coalescência nula ?? para especificar uma expressão alternativa a ser avaliada caso o resultado de uma operação condicional nula seja null.

Se a.x ou a[x] é de um tipo Tde valor não anulável , a?.x ou a?[x] é do tipoT?de valor anulável correspondente . Se precisar de uma expressão do tipo T, aplique o operador de coalescência nula ?? a uma expressão condicional nula, como no 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 falsenumbers é null.

Nota

O ?. operador avalia o seu operando esquerdo apenas uma vez, garantindo que ele não possa ser alterado para null após ser verificado como não-nulo.

A partir do C# 14, a atribuição é permitida com uma expressão de acesso condicional nula (?. e ?[]) em tipos de referência. Por exemplo, consulte o seguinte método:

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

O exemplo anterior mostra a atribuição a uma propriedade e a um elemento indexado num tipo de referência que pode ser nulo. Um comportamento importante para esta tarefa é que a expressão no lado direito do = é avaliada somente quando se sabe que o lado esquerdo não é nulo. Por exemplo, no código a seguir, a função GenerateNextIndex é chamada somente quando a values matriz não é nula. Se a values matriz for nula, GenerateNextIndex não será chamada:

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

Em outras palavras, o código anterior é equivalente ao código a seguir usando uma if instrução para a verificação nula:

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

Além da atribuição, qualquer forma de atribuição composta, como += ou -=, são permitidas. No entanto, incremento (++) e decréscimo (--) não são permitidos.

Esse aprimoramento não classifica uma expressão condicional nula como uma variável. Ele não pode ser ref atribuído, nem pode ser atribuído a uma ref variável ou passado para um método como um ref ou out argumento.

Invocação de delegado seguro para threads

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

PropertyChanged?.Invoke(…)

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

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

O exemplo anterior é uma forma segura para threads de garantir que apenas um handler não nulo 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 ()

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

O código 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 operador new.

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 seja «contável». Um tipo enumerável é um tipo que tem uma propriedade chamada int ou Count com um acessador que pode ser acedido Length. As expressões de coleção também dependem de tipos contáveis .

Nota

Matrizes unidimensionais são contáveis. Matrizes multidimensionais não o são. Os operadores ^ e .. (intervalo) não podem ser usados em matrizes multidimensionais.

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 tipo System.Index. Na expressão ^e, o resultado de e deve ser implicitamente conversível para 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 intervalo ..

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 à direita é um fim exclusivo 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 ^e é do tipo System.Index. Na expressão , os resultados de e devem ser implicitamente convertíveis para ou .

Importante

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

Pode omitir qualquer um dos operandos do operador .. para obter um intervalo sem limite:

  • a.. é equivalente a a..^0
  • a.. é equivalente a a..^0
  • a.. é equivalente a a..^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 Descrição
.. Todos os valores na coleção.
..end Valores desde o começo até a end exclusivo.
start.. Valores desde o start inclusive até ao fim.
start..end Valores de start inclusive a end exclusivo.
^start.. Valores desde o start inclusive até ao final, contando a partir do final.
..^end Valores desde o início até ao end, contados exclusivamente a partir do fim.
start..^end Valores de start inclusivamente a end exclusivamente, contando a partir do final.
^start..^end Valores de start inclusivos a end exclusivos, ambos contados 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 de propagação numa expressão de coleção.

Capacidade de sobrecarga do operador

Os ., (), ^, e .. operadores não podem ser sobrecarregados. O [] operador também é considerado um operador não sobrecarregável. 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 da proposta de funcionalidade.

Consulte também