Operadores y expresiones de acceso a miembros: los operadores de punto, indexadores y de invocación.
Se usan varios operadores y expresiones para acceder a un miembro de tipo. Estos operadores incluyen el acceso a miembros (.
), el elemento de matriz o el acceso al indexador ([]
), el índice desde el extremo (), el intervalo (..
^
), los operadores condicionales NULL (?.
y ?[]
) y la invocación del método (()
). Entre ellos se incluyen los operadores de acceso a miembros condicionales NULL (?.
) y acceso al indexador (?[]
).
.
(acceso a miembros): para acceder a un miembro de un espacio de nombres o un tipo[]
(elemento de matriz o acceso a indizador): para acceder a un elemento de matriz o un indizador de tipo?.
y?[]
(operadores condicionales NULL): para realizar una operación de acceso a elementos o miembros solo si un operando es distinto de NULL()
(invocación): para llamar a un método de acceso o invocar un delegado^
(índice desde el final) : para indicar que la posición del elemento se encuentra a partir del final de una secuencia..
(intervalo): para especificar un intervalo de índices que puede utilizar para obtener un intervalo de elementos de secuencia
Expresión de acceso a miembros .
Use el token .
para acceder a un miembro de un espacio de nombres o un tipo, como se muestran en los ejemplos siguientes:
- Use
.
para acceder a un espacio de nombres anidado dentro de un espacio de nombres, como se muestra en el siguiente ejemplo de una directivausing
:
using System.Collections.Generic;
- Use
.
para formar un nombre completo para tener acceso a un tipo dentro de un espacio de nombres, como se muestra en el código siguiente:
System.Collections.Generic.IEnumerable<int> numbers = [1, 2, 3];
Utilice una directiva using
para hacer que el uso de nombres completos sea opcional.
- Use
.
para acceder a los miembros del tipo, estáticos y no estáticos, tal como muestra el siguiente código:
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
También puede usar .
para acceder a un método de extensión.
Operador del indizador []
Los corchetes, []
, se suelen usar para el acceso a matriz, indizador o elemento de puntero. A partir de C# 12, []
incluye una expresión de colección.
Acceso a matriz
En el ejemplo siguiente se muestra cómo se obtiene acceso a los 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
Si un índice de matriz se encuentra fuera de los límites de la dimensión correspondiente de una matriz, se produce una excepción IndexOutOfRangeException.
Tal como se muestra en el ejemplo anterior, también usa corchetes al declarar un tipo de matriz o crear instancias de matriz.
Para obtener más información sobre las matrices, consulte Matrices.
Acceso a indizador
En el ejemplo siguiente se usa el tipo Dictionary<TKey,TValue> de .NET para mostrar el acceso al indizador:
var dict = new Dictionary<string, double>();
dict["one"] = 1;
dict["pi"] = Math.PI;
Console.WriteLine(dict["one"] + dict["pi"]); // output: 4.14159265358979
Los indizadores le permiten indizar las instancias de un tipo definido por el usuario de un modo similar a la indización de matrices. A diferencia de los índices de matriz, que deben ser enteros, los parámetros de indizador se pueden declarar para ser de cualquier tipo.
Para más información sobre los indizadores, consulte Indizadores.
Otros usos de []
Para información sobre el acceso de los elementos de puntero, consulte la sección Operador de acceso de elemento de puntero del artículo Operadores relacionados con el puntero. Para obtener información sobre las expresiones de colección, consulte el artículo Expresiones de colección.
También usa los corchetes para especificar atributos:
[System.Diagnostics.Conditional("DEBUG")]
void TraceMethod() {}
Operadores condicionales NULL ?.
y ?[]
Un operador condicional null aplica un acceso a miembros (?.
) o la operación acceso a elementos (?[]
) a su operando solo si ese operando se evalúa como no null; de lo contrario, devuelve null
. En otras palabras:
Si
a
se evalúa comonull
, el resultado dea?.x
oa?[x]
esnull
.Si
a
se evalúa como no NULL, el resultado dea?.x
oa?[x]
es el mismo que el resultado dea.x
oa[x]
, respectivamente.Nota
Si
a.x
oa[x]
producen una excepción,a?.x
oa?[x]
produciría la misma excepción paraa
no NULL. Por ejemplo, sia
es una instancia de matriz que no es NULL yx
está fuera de los límites dea
,a?[x]
produciría una excepción IndexOutOfRangeException.
Los operadores de condición NULL se cortocircuitan. Es decir, si una operación en una cadena de la operación de acceso a elementos o miembros condicional devuelve null
, no se ejecuta el resto de la cadena. En el ejemplo siguiente, B
no se evalúa si A
se evalúa como null
y C
no se evalúa si A
o B
se evalúan como null
:
A?.B?.Do(C);
A?.B?[C];
Si A
podría ser NULL, pero B
y C
no lo serían si A no lo es también, solo tiene que aplicar el operador condicional NULL a A
:
A?.B.C();
En el ejemplo anterior, B
no se evalúa y no se llama a C()
si A
es NULL. Sin embargo, si se interrumpe el acceso a miembros encadenados, por ejemplo, entre paréntesis como en (A?.B).C()
, no se produciría un cortocircuito.
En los ejemplos siguientes se muestra el uso de los operadores ?.
y ?[]
:
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}");
}
En el primero de los dos ejemplos anteriores también se usa el operador de fusión de NULL ??
para especificar una expresión alternativa que se evaluará en caso de que el resultado de la operación condicional NULL sea null
.
Si a.x
o a[x]
es de un tipo de valor que no admite un valor NULL, T
, a?.x
o a?[x]
es del tipo de valor que admite un valor NULL T?
correspondiente. Si necesita una expresión de tipo T
, aplique el operador de fusión de NULL ??
a una expresión condicional NULL, tal como se muestra en el ejemplo siguiente:
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
En el ejemplo anterior, si no utiliza el operador ??
, numbers?.Length < 2
da como resultado false
cuando numbers
es null
.
Nota
El operador ?.
evalúa el operando de la izquierda no más de una vez, lo que garantiza que no se pueda cambiar a null
después de verificarse como no NULL.
El operador de acceso de miembro condicional NULL ?.
también se conoce con el nombre de operador Elvis.
Invocación de delegado seguro para subprocesos
Use el operador ?.
para comprobar si un delegado es distinto de NULL y se invoca de forma segura para subprocesos (por ejemplo, cuando se genera un evento), tal como se muestra en el código siguiente:
PropertyChanged?.Invoke(…)
El código es equivalente al siguiente:
var handler = this.PropertyChanged;
if (handler != null)
{
handler(…);
}
El ejemplo anterior es una manera segura para subprocesos para asegurarse de que solo se invoca un valor no NULL handler
. Dado que las instancias de delegado son inmutables, ningún subproceso puede cambiar el valor al que hace referencia la variable local handler
. En concreto, si el código que ha ejecutado otro subproceso cancela la suscripción del evento PropertyChanged
y PropertyChanged
se convierte en null
antes de que se invoque handler
, el objeto al que hace referencia handler
queda intacto.
Expresión de invocación ()
Utilice paréntesis, ()
, para llamar a un método o invocar un delegado.
En el ejemplo siguiente se muestra cómo llamar a un método, con o sin argumentos, y cómo invocar un 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
También usa paréntesis al invocar un constructor con el operador new
.
Otros usos de ()
También usa los paréntesis para ajustar el orden en el que se van a evaluar operaciones en una expresión. Para obtener más información, vea Operadores de C# (referencia de C#).
Expresiones de conversión, que realizan conversiones de tipo explícitas, también utilizan paréntesis.
Indexación desde el operador final ^
Los operadores de índice e intervalo se pueden usar con un tipo contable. Un tipo contable es un tipo que tiene una propiedad int
denominada Count
o Length
con un descriptor de acceso get
accesible. Las expresiones de colección también se basan en tipos contables.
El operador ^
indica la posición del elemento a partir del final de una secuencia. En el caso de una secuencia de longitud length
, ^n
apunta al elemento con desplazamiento length - n
desde el inicio de una secuencia. Por ejemplo, ^1
apunta al último elemento de una secuencia y ^length
apunta al primer elemento de una secuencia.
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 se muestra en el ejemplo anterior, la expresión ^e
es del tipo System.Index. En la expresión ^e
, el resultado de e
debe poderse convertir implícitamente a int
.
También puede usar el operador ^
con el operador de intervalo para crear un intervalo de índices. Para más información, consulte Índices y rangos.
A partir de C# 13, el índice del operador final se puede usar en un inicializador de objeto.
Operador de intervalo ..
El operador ..
especifica el inicio y el final de un intervalo de índices como sus operandos. El operando izquierdo es un inicio inclusivo de un intervalo. El operando derecho es un inicio exclusivo de un intervalo. Cualquiera de los operandos puede ser un índice desde el inicio o desde el final de una secuencia, tal y como muestra el ejemplo siguiente:
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 se muestra en el ejemplo anterior, la expresión a..b
es del tipo System.Range. En la expresión a..b
, los resultados de a
y b
deben poderse convertir implícitamente a Int32 o Index.
Importante
Las conversiones implícitas de int
a Index
producen una excepción ArgumentOutOfRangeException cuando el valor es negativo.
Puede omitir cualquiera de los operandos del operador ..
para obtener un intervalo abierto:
a..
es equivalente aa..^0
..b
es equivalente a0..b
..
es equivalente 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));
En la tabla siguiente se muestran varias maneras de expresar los intervalos de colección:
Expresión del operador de intervalo | Descripción |
---|---|
.. |
Todos los valores de la colección. |
..end |
Valores desde el principio hasta end exclusivamente. |
start.. |
Valores desde start inclusivamente hasta el final. |
start..end |
Valores desde start inclusivamente hasta end exclusivamente. |
^start.. |
Valores desde start inclusivamente hasta el final contando desde el final. |
..^end |
Valores desde el inicio hasta end exclusivamente contando desde el final. |
start..^end |
Valores de start inclusivamente a end exclusivamente contando desde el final. |
^start..^end |
Valores de start inclusivamente a end exclusivamente contando ambos desde el final. |
En el ejemplo siguiente se muestra el efecto de usar todos los intervalos presentados en la tabla 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 más información, consulte Índices y rangos.
El token ..
también se usa para el elemento de propagación en una expresión de colección.
Posibilidad de sobrecarga del operador
Los operadores .
, ()
, ^
y ..
no se pueden sobrecargar. El operador []
también se considera un operador que no se puede sobrecargar. Use indizadores para admitir la indización con tipos definidos por el usuario.
Especificación del lenguaje C#
Para más información, vea las secciones siguientes de la Especificación del lenguaje C#:
- Acceso a miembros
- Acceso a elementos
- El operador "null conditional member access"
- Expresiones de invocación
Para más información sobre índices y rangos, vea la nota de propuesta de características.