Uso de indizadores (Guía de programación de C#)
Los indizadores son una comodidad sintáctica que le permiten crear una clase, estructura o interfaz a la que las aplicaciones cliente pueden acceder como una matriz. El compilador genera una propiedad Item
(o una propiedad con otro nombre si está presente IndexerNameAttribute) y los métodos de descriptor de acceso adecuados. Los indexadores se implementan con más frecuencia en tipos cuyo propósito principal consiste en encapsular una matriz o colección interna. Por ejemplo, imagine que tiene una clase TempRecord
que representa la temperatura en grados Fahrenheit que se registra en 10 momentos diferentes durante un período de 24 horas. La clase contiene una matriz temps
de tipo float[]
para almacenar los valores de temperatura. Si implementa un indizador en esta clase, los clientes pueden tener acceso a las temperaturas en una instancia de TempRecord
como float temp = tempRecord[4]
en lugar de como float temp = tempRecord.temps[4]
. La notación del indizador no solo simplifica la sintaxis para las aplicaciones cliente; también hace que la clase y su finalidad sean más intuitivas para que otros desarrolladores las entiendan.
Para declarar un indizador en una clase o un struct, use la palabra clave this, como en este ejemplo:
// Indexer declaration
public int this[int index]
{
// get and set accessors
}
Importante
Al declarar un indizador, se generará automáticamente una propiedad denominada Item
en el objeto. No se puede acceder directamente a la propiedad Item
desde la instancia de expresión de acceso a miembros. Además, si agrega una propiedad Item
propia a un objeto con un indizador, obtendrá un error del compilador CS0102. Para evitar este error, use el IndexerNameAttribute para cambiar de nombre el indizador como se detalla más adelante en este artículo.
Comentarios
Los tipos de un indexador y de sus parámetros deben ser al menos igual de accesibles que el propio indexador. Para obtener más información sobre los niveles de accesibilidad, vea Modificadores de acceso.
Para obtener más información sobre cómo usar los indexadores con una interfaz, vea Indizadores en interfaces.
La firma de un indexador consta del número y los tipos de sus parámetros formales. No incluye el tipo de indizador ni los nombres de los parámetros formales. Si declara más de un indexador en la misma clase, deben tener firmas diferentes.
Un indizador no se clasifica como una variable; por lo tanto, un valor de indizador no se puede pasar por referencia (como un parámetro ref
o out
) a menos que su valor sea una referencia (es decir, se devuelve por referencia).
Para proporcionar el indizador con un nombre que puedan usar otros lenguajes, use System.Runtime.CompilerServices.IndexerNameAttribute, como se muestra en el ejemplo siguiente:
// Indexer declaration
[System.Runtime.CompilerServices.IndexerName("TheItem")]
public int this[int index]
{
// get and set accessors
}
Este indizador tiene el nombre TheItem
, ya que lo reemplaza el atributo de nombre del indizador. De forma predeterminada, el nombre del indizador es Item
.
Ejemplo 1
En el ejemplo siguiente, se muestra cómo declarar un campo de matriz privada, temps
, como un indexador. El indexador permite el acceso directo a la instancia tempRecord[i]
. La alternativa a usar el indexador es declarar la matriz como un miembro public y tener acceso directamente a sus miembros tempRecord.temps[i]
.
public class TempRecord
{
// Array of temperature values
float[] temps =
[
56.2F, 56.7F, 56.5F, 56.9F, 58.8F,
61.3F, 65.9F, 62.1F, 59.2F, 57.5F
];
// To enable client code to validate input
// when accessing your indexer.
public int Length => temps.Length;
// Indexer declaration.
// If index is out of range, the temps array will throw the exception.
public float this[int index]
{
get => temps[index];
set => temps[index] = value;
}
}
Tenga en cuenta que, cuando se evalúa el acceso de un indexador (por ejemplo, en una instrucción Console.Write
), se invoca al descriptor de acceso get. Por tanto, si no hay ningún descriptor de acceso get
, se produce un error en tiempo de compilación.
var tempRecord = new TempRecord();
// Use the indexer's set accessor
tempRecord[3] = 58.3F;
tempRecord[5] = 60.1F;
// Use the indexer's get accessor
for (int i = 0; i < 10; i++)
{
Console.WriteLine($"Element #{i} = {tempRecord[i]}");
}
Indexación con otros valores
C# no limita el tipo de parámetro indizador a un entero. Por ejemplo, puede ser útil usar una cadena con un indizador. Este tipo de indexador podría implementarse al buscar la cadena de la colección y devolver el valor adecuado. Como los descriptores de acceso se pueden sobrecargar, pueden coexistir las versiones de cadena y entero.
Ejemplo 2
En el ejemplo siguiente se declara una clase que almacena los días de la semana. Un descriptor de acceso get
toma una cadena, el nombre de un día, y devuelve el entero correspondiente. Por ejemplo, "Sunday" devuelve 0, "Monday" devuelve 1 y así sucesivamente.
// Using a string as an indexer value
class DayCollection
{
string[] days = ["Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat"];
// Indexer with only a get accessor with the expression-bodied definition:
public int this[string day] => FindDayIndex(day);
private int FindDayIndex(string day)
{
for (int j = 0; j < days.Length; j++)
{
if (days[j] == day)
{
return j;
}
}
throw new ArgumentOutOfRangeException(
nameof(day),
$"Day {day} is not supported.\nDay input must be in the form \"Sun\", \"Mon\", etc");
}
}
Ejemplo de consumo 2
var week = new DayCollection();
Console.WriteLine(week["Fri"]);
try
{
Console.WriteLine(week["Made-up day"]);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine($"Not supported input: {e.Message}");
}
Ejemplo 3
En el ejemplo siguiente se declara una clase que almacena los días de la semana mediante la enumeración System.DayOfWeek. Un descriptor de acceso get
toma un valor DayOfWeek
, el nombre de un día, y devuelve el entero correspondiente. Por ejemplo, DayOfWeek.Sunday
devuelve 0, DayOfWeek.Monday
devuelve 1, y así sucesivamente.
using Day = System.DayOfWeek;
class DayOfWeekCollection
{
Day[] days =
[
Day.Sunday, Day.Monday, Day.Tuesday, Day.Wednesday,
Day.Thursday, Day.Friday, Day.Saturday
];
// Indexer with only a get accessor with the expression-bodied definition:
public int this[Day day] => FindDayIndex(day);
private int FindDayIndex(Day day)
{
for (int j = 0; j < days.Length; j++)
{
if (days[j] == day)
{
return j;
}
}
throw new ArgumentOutOfRangeException(
nameof(day),
$"Day {day} is not supported.\nDay input must be a defined System.DayOfWeek value.");
}
}
Ejemplo de consumo 3
var week = new DayOfWeekCollection();
Console.WriteLine(week[DayOfWeek.Friday]);
try
{
Console.WriteLine(week[(DayOfWeek)43]);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine($"Not supported input: {e.Message}");
}
Programación sólida
Hay dos formas principales en que se pueden mejorar la seguridad y confiabilidad de los indexadores:
Asegúrese de incorporar algún tipo de estrategia de control de errores para controlar la posibilidad de que el código de cliente pase un valor de índice no válido. En el primer ejemplo de este artículo, la clase TempRecord proporciona una propiedad Length que permite al código de cliente comprobar la entrada antes de pasarla al indizador. También puede colocar el código de control de errores en el propio indexador. Asegúrese de documentar para los usuarios cualquier excepción que se produzca dentro de un descriptor de acceso del indexador.
Establezca la accesibilidad de los descriptores de acceso get and set para que sea tan restrictiva como razonable. Esto es importante para el descriptor de acceso
set
en particular. Para más información, vea Restringir la accesibilidad del descriptor de acceso.