1. Indexadores con varios parámetros
Un indexador simple (por ejemplo, this[int index]) es útil cuando necesitas acceder a elementos por un índice entero — casi como en un array. Pero C# permite mucho más: puedes usar varios parámetros, parámetros de diferentes tipos, poner diferentes modificadores de acceso para get y set, e incluso implementar varios indexadores en una clase (si las firmas son distintas).
Además, en las nuevas versiones de C# hay características extra que facilitan la vida al trabajar con indexadores, incluyendo azúcar sintáctico para escribir código más limpio.
Un indexador no tiene por qué aceptar solo un índice y solo del tipo int. Los parámetros los defines tú — y puedes tener varios parámetros de distintos tipos si tu caso lo necesita.
public class ChessBoard
{
private string[,] board = new string[8, 8];
// ¡Indexador con dos parámetros!
public string this[int row, int col]
{
get { return board[row, col]; }
set { board[row, col] = value; }
}
}
Ahora podemos escribir código así:
ChessBoard chess = new ChessBoard();
chess[0, 0] = "Torre";
chess[7, 7] = "Rey";
string piece = chess[0, 0]; // "Torre"
Este enfoque va genial para matrices, mapas bidimensionales, juegos de mesa, colecciones complejas.
2. Indexadores con parámetros de diferentes tipos
Tu indexador puede aceptar parámetros no solo del tipo int, sino de cualquier otro tipo que tenga sentido para tu caso. Lo importante es que sea lógico para tu tarea.
using System.Collections.Generic;
public class Employee
{
public string Name { get; set; }
public int Age { get; set; }
public string Position { get; set; }
}
public class EmployeeCollection
{
private List<Employee> employees = new List<Employee>();
// Indexador por nombre del empleado (string)
public Employee this[string name]
{
get
{
foreach (var employee in employees)
{
if (employee.Name == name)
return employee;
}
return null; // O puedes lanzar una excepción
}
set
{
for (int i = 0; i < employees.Count; i++)
{
if (employees[i].Name == name)
{
employees[i] = value;
return;
}
}
// Si no existe un empleado con ese nombre — añadimos uno nuevo
employees.Add(value);
}
}
// Para compatibilidad — indexador por índice numérico
public Employee this[int index]
{
get { return employees[index]; }
set { employees[index] = value; }
}
}
var company = new EmployeeCollection();
company[0] = new Employee { Name = "Ivan", Age = 30, Position = "Programador" };
company[1] = new Employee { Name = "Maria", Age = 25, Position = "Diseñadora" };
Employee employee = company["Ivan"];
company["Petr"] = new Employee { Name = "Petr", Age = 28, Position = "Tester" };
Importante: si tienes varios indexadores — sus firmas deben diferenciarse por el conjunto y tipo de parámetros.
3. Diferentes modificadores de acceso para get y set
A veces necesitas permitir solo lectura por índice y prohibir la escritura (o al revés). En C# puedes poner diferentes modificadores de acceso para los accesores get y set de un indexador.
public class SecureEmployeeCollection
{
private List<Employee> employees = new List<Employee>();
public Employee this[int index]
{
get { return employees[index]; }
internal set { employees[index] = value; }
}
}
Esto se usa mucho para proteger colecciones de cambios no autorizados, haciendo la clase más controlada.
4. Indexadores solo de lectura y solo de escritura
A veces quieres permitir solo lectura o solo escritura a través del indexador.
public class ReadOnlyEmployeeCollection
{
private List<Employee> employees = new List<Employee>();
public Employee this[int index]
{
get { return employees[index]; }
// set ausente — ¡no se puede modificar!
}
}
public class WriteOnlyEmployeeCollection
{
private List<Employee> employees = new List<Employee>();
public Employee this[int index]
{
set
{
employees.Insert(index, value);
}
// get ausente — ¡no se puede leer!
}
}
En proyectos reales, el "write-only" casi nunca se ve: normalmente se usan "read-only", por ejemplo, cuando desde fuera de la clase solo se puede mirar pero no modificar los datos por el indexador.
5. Indexadores con comprobación de límites y lógica
En buenos ejemplos, no solo hay que dar acceso por índice, sino también manejar correctamente los desbordamientos, errores de búsqueda y otras situaciones excepcionales.
public class SafeEmployeeCollection
{
private List<Employee> employees = new List<Employee>();
public Employee this[int index]
{
get
{
if (index < 0 || index >= employees.Count)
throw new IndexOutOfRangeException("¡No existe un empleado con ese índice!");
return employees[index];
}
set
{
if (index < 0 || index >= employees.Count)
throw new IndexOutOfRangeException("¡No se puede reemplazar a un empleado que no existe!");
employees[index] = value;
}
}
}
Puedes devolver null o usar patrones modernos para manejar valores ausentes — depende de la lógica de tu app.
6. Indexadores con parámetros poco comunes
Puedes encontrarte colecciones donde el indexador acepta tipos no estándar: enumeraciones (enum), structs personalizados, incluso varios parámetros de diferentes tipos.
public enum Department { IT, HR, Finance, Marketing }
public class DepartmentEmployeeCollection
{
private Dictionary<Department , Employee> departmentLeads = new Dictionary<Department , Employee>();
public Employee this[Department department]
{
get { return departmentLeads.TryGetValue(department, out var employee) ? employee : null; }
set { departmentLeads[department] = value; }
}
}
var company = new DepartmentEmployeeCollection();
company[Department.IT] = new Employee { Name = "Anna", Age = 35, Position = "Jefa IT" };
Employee itLead = company[Department.IT];
Este enfoque se usa cuando tienes identificadores únicos o una correspondencia clara entre tipo y valor — cómodo, claro y seguro en tipos.
7. Características modernas: Range e Index
Con la llegada de los tipos Range y Index en C# 8, los indexadores tienen nuevas posibilidades para trabajar con rangos e índices desde el final:
public class SmartArray
{
private int[] numbers = Enumerable.Range(0, 100).ToArray();
public int[] this[Range range] => numbers[range];
public int this[Index index] => numbers[index];
}
// Uso:
var smart = new SmartArray();
int[] middle = smart[20..30]; // del elemento 20 al 29
int last = smart[^1]; // último elemento
Si implementas tu propia colección, soportar Range y Index la hace súper "nativa" y cómoda para los desarrolladores.
8. Propiedades vs indexadores
Para entender mejor cuándo usar propiedades y cuándo indexadores, es útil comparar sus características.
- Las propiedades son cómodas para acceder a características individuales de un objeto por nombre: person.Name, car.Speed.
- Los indexadores van bien para acceder a elementos de una colección o estructura por clave: employees[0], phoneBook["Ivan"].
- Una propiedad siempre tiene un nombre concreto y en una clase solo puede haber una propiedad con ese nombre.
- El indexador usa la palabra clave this y puede tener varias variantes si sus firmas son distintas.
- Las propiedades pueden ser estáticas, los indexadores no, porque this siempre apunta a una instancia concreta del objeto.
9. Errores típicos al trabajar con indexadores
Error nº1: no comprobar los límites.
Si no compruebas el índice para evitar salirte del array, puedes tener un IndexOutOfRangeException en cualquier momento.
Error nº2: no manejar el posible null del indexador.
Si el indexador por string devuelve null cuando no hay elemento, y el código que lo llama usa el resultado sin pensar, aparece un NullReferenceException.
Error nº3: duplicar firmas de indexadores.
C# no deja crear dos indexadores con los mismos parámetros.
Error nº4: lógica poco clara en el accesor set.
Si en el set usas lógica de "añadir si no existe" en vez de reemplazar, puede confundir. Estas decisiones deberían ser explícitas y estar bien documentadas.
10. Aplicación práctica y conclusión
En proyectos reales, los indexadores se usan mucho para crear colecciones especializadas, cachés, diccionarios con lógica extra, matrices y estructuras de datos multidimensionales. Hacen el código más legible e intuitivo — en vez de llamar métodos como GetElementByIndex(5) puedes simplemente escribir collection[5].
Recuerda: los indexadores deben tener sentido lógico para la naturaleza de tu clase. Si la clase no es una colección o estructura de datos, probablemente no necesita un indexador. Pero si tu clase guarda y gestiona un conjunto de elementos, el indexador puede hacer su uso mucho más cómodo y natural.
GO TO FULL VERSION