Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Los indexadores se definen cuando las instancias de una clase o estructura se pueden indexar como una matriz u otra colección. El valor indexado se puede establecer o recuperar sin especificar explícitamente un tipo o un miembro de instancia. Los indexadores se asemejan a las propiedades , excepto que sus descriptores de acceso toman parámetros.
En el ejemplo siguiente se define una clase genérica con los métodos de acceso get
y set
para asignar y recuperar valores.
namespace Indexers;
public class SampleCollection<T>
{
// Declare an array to store the data elements.
private T[] arr = new T[100];
// Define the indexer to allow client code to use [] notation.
public T this[int i]
{
get => arr[i];
set => arr[i] = value;
}
}
En el ejemplo anterior se muestra un indexador de lectura y escritura. Contiene tanto los accesores de get
como de set
. Puede definir indexadores de solo lectura como un miembro con forma de expresión, como se muestra en los ejemplos siguientes:
namespace Indexers;
public class ReadOnlySampleCollection<T>(params IEnumerable<T> items)
{
// Declare an array to store the data elements.
private T[] arr = [.. items];
public T this[int i] => arr[i];
}
La get
palabra clave no se usa; =>
presenta el cuerpo de la expresión.
Los indexadores habilitan propiedades indexadas : propiedades a las que se hace referencia mediante uno o varios argumentos. Esos argumentos proporcionan un índice en algunas colecciones de valores.
- Los indexadores permiten que los objetos se indexan de forma similar a las matrices.
- Un
get
accesor devuelve un valor. Unset
accesor establece un valor. - La
this
palabra clave define el indexador. - La palabra clave
value
es el argumento del descriptor de accesoset
. - Los indexadores no requieren un valor de índice entero; es necesario definir el mecanismo de búsqueda específico.
- Los indexadores se pueden sobrecargar.
- Los indexadores pueden tener uno o varios parámetros formales, por ejemplo, al acceder a una matriz bidimensional.
- Puede declarar indexadores
partial
en tipospartial
.
Puede aplicar casi todo lo que ha aprendido de trabajar con propiedades a indexadores. La única excepción a esa regla son las propiedades implementadas automáticamente. El compilador no siempre puede generar el almacenamiento correcto para un indexador. Puede definir varios indexadores en un tipo, siempre y cuando las listas de argumentos de cada indexador sean únicas.
Usos de indizadores
Defina los indexadores en su tipo cuando su API modele alguna colección. No es necesario que su indexador se asigne directamente a los tipos de colección que forman parte del marco de trabajo principal de .NET. Los indexadores permiten proporcionar la API que coincide con la abstracción del tipo sin exponer los detalles internos de cómo se almacenan o calculan los valores de esa abstracción.
Matrices y vectores
Su tipo podría modelar una matriz o un vector. La ventaja de crear su propio indexador es que puede definir el almacenamiento de esa colección para satisfacer sus necesidades. Imagine un escenario en el que los datos históricos de los modelos de tipo son demasiado grandes para cargarlos en la memoria a la vez. Debe cargar y descargar secciones de la colección en función del uso. En el ejemplo siguiente se modela este comportamiento. Informa sobre cuántos puntos de datos existen. Crea páginas para contener secciones de los datos a petición. Elimina las páginas de la memoria para hacer espacio para las páginas necesarias para solicitudes más recientes.
namespace Indexers;
public record Measurements(double HiTemp, double LoTemp, double AirPressure);
public class DataSamples
{
private class Page
{
private readonly List<Measurements> pageData = new ();
private readonly int _startingIndex;
private readonly int _length;
public Page(int startingIndex, int length)
{
_startingIndex = startingIndex;
_length = length;
// This stays as random stuff:
var generator = new Random();
for (int i = 0; i < length; i++)
{
var m = new Measurements(HiTemp: generator.Next(50, 95),
LoTemp: generator.Next(12, 49),
AirPressure: 28.0 + generator.NextDouble() * 4
);
pageData.Add(m);
}
}
public bool HasItem(int index) =>
((index >= _startingIndex) &&
(index < _startingIndex + _length));
public Measurements this[int index]
{
get
{
LastAccess = DateTime.Now;
return pageData[index - _startingIndex];
}
set
{
pageData[index - _startingIndex] = value;
Dirty = true;
LastAccess = DateTime.Now;
}
}
public bool Dirty { get; private set; } = false;
public DateTime LastAccess { get; set; } = DateTime.Now;
}
private readonly int _totalSize;
private readonly List<Page> pagesInMemory = new ();
public DataSamples(int totalSize)
{
this._totalSize = totalSize;
}
public Measurements this[int index]
{
get
{
if (index < 0) throw new IndexOutOfRangeException("Cannot index less than 0");
if (index >= _totalSize) throw new IndexOutOfRangeException("Cannot index past the end of storage");
var page = updateCachedPagesForAccess(index);
return page[index];
}
set
{
if (index < 0) throw new IndexOutOfRangeException("Cannot index less than 0");
if (index >= _totalSize) throw new IndexOutOfRangeException("Cannot index past the end of storage");
var page = updateCachedPagesForAccess(index);
page[index] = value;
}
}
private Page updateCachedPagesForAccess(int index)
{
foreach (var p in pagesInMemory)
{
if (p.HasItem(index))
{
return p;
}
}
var startingIndex = (index / 1000) * 1000;
var newPage = new Page(startingIndex, 1000);
addPageToCache(newPage);
return newPage;
}
private void addPageToCache(Page p)
{
if (pagesInMemory.Count > 4)
{
// remove oldest non-dirty page:
var oldest = pagesInMemory
.Where(page => !page.Dirty)
.OrderBy(page => page.LastAccess)
.FirstOrDefault();
// Note that this may keep more than 5 pages in memory
// if too much is dirty
if (oldest != null)
pagesInMemory.Remove(oldest);
}
pagesInMemory.Add(p);
}
}
Puede seguir esta expresión de diseño para modelar cualquier tipo de colección en la que haya buenas razones para no cargar todo el conjunto de datos en una colección en memoria. Observe que la Page
clase es una clase anidada privada que no forma parte de la interfaz pública. Esos detalles están ocultos de los usuarios de esta clase.
Diccionarios
Otro escenario común es cuando necesita modelar un diccionario o un mapa. En este escenario, el tipo almacena valores en función de la clave, posiblemente claves de texto. En este ejemplo se crea un diccionario que asigna argumentos de línea de comandos a expresiones lambda que administran esas opciones. En el siguiente ejemplo se muestran dos clases: ArgsActions
clase que asigna una opción de línea de comandos a un System.Action delegado, y una ArgsProcessor
que usa ArgsActions
para ejecutar cada Action cuando encuentra esa opción.
namespace Indexers;
public class ArgsProcessor
{
private readonly ArgsActions _actions;
public ArgsProcessor(ArgsActions actions)
{
_actions = actions;
}
public void Process(string[] args)
{
foreach (var arg in args)
{
_actions[arg]?.Invoke();
}
}
}
public class ArgsActions
{
readonly private Dictionary<string, Action> _argsActions = new();
public Action this[string s]
{
get
{
Action? action;
Action defaultAction = () => { };
return _argsActions.TryGetValue(s, out action) ? action : defaultAction;
}
}
public void SetOption(string s, Action a)
{
_argsActions[s] = a;
}
}
En este ejemplo, la colección ArgsAction
está estrechamente relacionada con la colección subyacente.
get
determina si se configura una opción determinada. Si es así, devuelve el Action asociado a esa opción. Si no es así, devuelve un Action valor que no hace nada. El descriptor de acceso público no incluye ningún descriptor de acceso set
. En su lugar, el diseño usa un método público para establecer opciones.
Mapas multidimensionales
Puede crear indizadores que usen varios argumentos. Además, esos argumentos no están restringidos para que sean del mismo tipo.
En el ejemplo siguiente se muestra una clase que genera valores para un conjunto de Mandelbrot. Para obtener más información sobre las matemáticas detrás del conjunto, lea este artículo. El indizador usa dos dobles para definir un punto en el plano X e Y. El get
descriptor de acceso calcula el número de iteraciones hasta que se determina que un punto no está en el conjunto. Cuando se alcanza el número máximo de iteraciones, el punto está en el conjunto y se devuelve el valor maxIterations de la clase. (Las imágenes generadas por computadora, popularizadas en el conjunto de Mandelbrot, asignan colores según el número de iteraciones necesarias para determinar que un punto está fuera del conjunto).
namespace Indexers;
public class Mandelbrot(int maxIterations)
{
public int this[double x, double y]
{
get
{
var iterations = 0;
var x0 = x;
var y0 = y;
while ((x * x + y * y < 4) &&
(iterations < maxIterations))
{
(x, y) = (x * x - y * y + x0, 2 * x * y + y0);
iterations++;
}
return iterations;
}
}
}
El conjunto de Mandelbrot define valores en cada coordenada (x,y) para los valores de número real. Que define un diccionario que podría contener un número infinito de valores. Por lo tanto, no hay ningún almacenamiento detrás del conjunto. En su lugar, esta clase calcula el valor de cada punto cuando el código llama al accesor get
. No se usa ningún almacenamiento subyacente.
Resumiendo
Los indexadores se crean en cualquier momento que tenga un elemento similar a una propiedad en la clase donde esa propiedad representa no un solo valor, sino un conjunto de valores. Uno o varios argumentos identifican cada elemento individual. Esos argumentos pueden identificar de forma única a qué elemento del conjunto se debe hacer referencia. Los indexadores amplían el concepto de propiedades, donde un miembro se trata como un elemento de datos desde fuera de la clase, pero como un método en el interior. Los indizadores permiten que los argumentos encuentren un solo elemento en una propiedad que represente un conjunto de elementos.
Puede acceder a la carpeta de ejemplo de los indexadores. Para obtener instrucciones de descarga, consulte Ejemplos y tutoriales.
Especificación del lenguaje C#
Para obtener más información, vea Indexadores en la especificación del lenguaje C#. La especificación del lenguaje es el origen definitivo de la sintaxis y el uso de C#.