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 (?[]
).
.
(acesso a membro): para acessar um membro de um namespace ou um tipo[]
(acesso a um indexador ou elemento de matriz): para acessar um elemento de matriz ou um indexador de tipo?.
e?[]
(operadores condicionais nulos): para executar uma operação de acesso a membro ou elemento somente se um operando for não nulo()
(invocação): chamar um método acessado ou invocar um delegado^
(índice do final): para indicar que a posição do elemento é do final de uma sequência..
(intervalo): para especificar um intervalo de índices que você pode usar para obter um intervalo de elementos de sequência
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 diretivausing
:
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 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 de indexador []
Os colchetes, []
, normalmente são usados para acesso de elemento de matriz, indexador ou ponteiro. A partir do C# 12, []
inclui uma expressão de coleção.
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. Para informações sobre expressões de coleção, consulte o artigo expressões de coleção.
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
. Em outras palavras:
Se
a
for avaliado comonull
, o resultado dea?.x
oua?[x]
seránull
.Se
a
for avaliado como não nulo, o resultado dea?.x
oua?[x]
será o mesmo resultado dea.x
oua[x]
, respectivamente.Observação
Se
a.x
oua[x]
lançarem uma exceção,a?.x
oua?[x]
lançarão a mesma exceção para não nuloa
. Por exemplo, sea
for uma instância de matriz não nula ex
estiver fora dos limites dea
,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ável T
, a?.x
ou a?[x]
serão do tipo de valor anulável T?
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.
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 à 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 aa..^0
..b
equivale a0..b
..
equivale a0..^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 para o elemento de propagação 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.