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

Você usa vários operadores e expressões para acessar um membro de tipo. Esses operadores incluem acesso de membro (.), acesso de elemento de matriz ou indexador ([]), índice de ponta (^), intervalo (..), operadores condicionais nulos (?. e ?[]), e invocação de método (()). Eles incluem os operadores de acesso de membro condicional nulo (?.) e de acesso do indexador (?[]).

Expressão de acesso a membro .

Use 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 a seguir de uma diretiva using:
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 diretiva using para tornar o uso de nomes qualificados opcional.

  • Use . para acessar membros de tipo, estático e não-estático, 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 de indexador []

Os colchetes, [], normalmente são usados para acesso de elemento de matriz, indexador ou ponteiro.

Acesso de matriz

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, uma IndexOutOfRangeException será gerada.

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

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

Acesso de indexador

O exemplo a seguir usa o tipo Dictionary<TKey,TValue> do .NET para demonstrar o acesso de 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 de maneira semelhante à indexação de matriz. Ao contrário dos índices da matriz, que precisam ser um inteiro, os parâmetros do indexador podem ser declarados como qualquer tipo.

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

Outros usos de []

Para saber mais sobre o acesso a elemento de ponteiro, confira a seção Operador de acesso a elemento de ponteiro [] do artigo Operadores relacionados a ponteiro.

Os colchetes também são usados para especificar atributos:

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

Operadores condicionais nulos ?. e ?[]

Um operador condicional nulo aplicará uma operação de acesso de membro (?.) ou de acesso de elemento (?[]) ao operando somente se esse operando for avaliado como não nulo; caso contrário, ele retornará null. Ou seja:

  • Se a for avaliado como null, o resultado de a?.x ou a?[x] será null.

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

    Observação

    Se a.x ou a[x] lançarem uma exceção, a?.x ou a?[x] lançarão 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çará um IndexOutOfRangeException.

Os operadores condicionais nulos estão entrando em curto-circuito. Ou seja, se uma operação em uma cadeia de membro operações condicionais de acesso a membro ou elemento retornar null, o restante da cadeia não será executado. No exemplo a seguir, B não será avaliado se A for avaliado como null e C não será avaliado se A ou B for avaliado como null:

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

Se A puder ser nulo, mas B e C não forem nulos se A não for nulo, você só precisará aplicar o operador condicional nulo a A:

A?.B.C();

No exemplo anterior, B não é avaliado e C() não será chamado se A for nulo. No entanto, se o acesso de membro encadeado for interrompido, por exemplo, por parênteses como em (A?.B).C(), o curto-circuito não ocorrerá.

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 de avaliação de nulo?? 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] forem de um tipo de valor não anulávelT, a?.x ou a?[x] serão do tipo de valor anulávelT? correspondente. Se você precisar de uma expressão de tipo T, aplique o operador de avaliação de nulo ?? a uma expressão condicional nula, 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 será avaliado como false quando numbers for null.

Observação

O operador ?. avalia seu operando à esquerda não mais do que uma vez, garantindo que ele não possa ser alterado para null após ser verificado como não nulo.

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

Invocação de delegado thread-safe

Use o operador ?. para verificar se um delegado é não nulo e chame-o de uma forma thread-safe (por exemplo, quando você aciona um evento), conforme mostrado no código a seguir:

PropertyChanged?.Invoke(…)

Esse código é equivalente ao seguinte:

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

O exemplo anterior é uma maneira thread-safe para garantir que apenas um handler não nulo seja invocado. Como as instâncias de delegado são imutáveis, nenhum thread pode alterar o objeto referenciado pela variável local handler. Em especial, se o código executado por outro thread cancelar a assinatura do evento PropertyChanged e PropertyChanged se tornar null antes de handler ser invocado, o objeto referenciado por handler permanecerá não afetado.

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 pode usar parênteses ao invocar um construtor com o operador new.

Outros usos de ()

Você também pode usar parênteses para ajustar a ordem na qual as operações em uma expressão são avaliadas. Para saber mais, confira Operadores C#.

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

Operador índice do final ^

Os operadores de índice e de intervalo podem ser usados com um tipo contável. Um tipo contável é um tipo que tem uma propriedade int chamada Count ou Length com um acessador get acessível. 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 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.

Operador de intervalo ..

O operador .. especifica o início e o fim de um intervalo de índices como seus operandos. O operando à esquerda é um início inclusivo de um intervalo. O operando à direita é um final exclusivo de um intervalo. Qualquer um dos operandos pode ser um índice desde o 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 tipo System.Range. Na expressão a..b, o resultado de a e b deve ser implicitamente conversível para Int32 ou Index.

Importante

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

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

  • a.. equivale a a..^0
  • ..b equivale a 0..b
  • .. equivale 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ções:

Expressão do operador de intervalo Descrição
.. Todos os valores na coleção.
..end Valores do início a end exclusivamente.
start.. Valores de start até e inclusive o final.
start..end Valores de start até e inclusive o end exclusivamente.
^start.. Valores de start até e inclusive a contagem final.
..^end Valores do início até a end excluindo a contagem final.
start..^end Valores de start até e inclusive end excluindo a contagem final.
^start..^end Valores de start até inclusive end excluindo ambas as contagens finais.

O exemplo a seguir demonstra o efeito de usar 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 como o operador de 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 sobrecarregável. Use indexadores para permitir a indexação com tipos definidos pelo usuário.

Especificação da linguagem C#

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

Para obter mais informações sobre índices e intervalos, confira a nota da proposta do recurso.

Confira também