1. ¿Para qué sirven las interfaces en las colecciones?
Seamos sinceros: mientras escribíamos código usando un tipo concreto (List<int>, Dictionary<string, string>), todo era fácil y claro. Pero imagina que de repente decides cambiar List<T> por HashSet<T> porque ahora solo necesitas valores únicos. O quieres crear un método que funcione tanto con uno como con otro tipo de colección. ¿Qué haces?
Para tener un único punto de entrada — necesitas un “conector universal”. En C#, ese conector son las interfaces. Definen un conjunto de reglas (métodos, propiedades) que debe tener una colección. Todo lo que implemente la interfaz — soporta esas operaciones.
Es como el cargador del móvil: si tienes un conector estándar USB-C, puedes cargar el móvil, una aspiradora moderna e incluso un cepillo de dientes eléctrico (lo importante es no confundirlos en la oscuridad).
Ejemplo práctico
Supón que quieres escribir un método que sume todos los números de cualquier colección de números. Así sería sin interfaces:
// Este método solo funciona con arrays:
static int SumArray(int[] array)
{
int sum = 0;
for (int i = 0; i < array.Length; i++)
{
sum += array[i];
}
return sum;
}
// Y este - solo con List<int>:
static int SumList(List<int> list)
{
int sum = 0;
for (int i = 0; i < list.Count; i++)
sum += list[i];
return sum;
}
¡Incómodo! El código es casi igual. Usando la interfaz IEnumerable<int> puedes hacerlo universal:
static int SumAll(IEnumerable<int> collection)
{
int sum = 0;
foreach (var number in collection)
sum += number;
return sum;
}
Y ahora este método acepta cualquier tipo de colección que implemente IEnumerable<int>: array, lista, conjunto, lo que sea.
2. Interfaces principales de las colecciones
En .NET hay varias interfaces clave para colecciones. Vamos a verlas por encima — en las siguientes lecciones verás los detalles de cada una.
| Interfaz | ¿Para qué sirve? | Ejemplo de colecciones que la implementan |
|---|---|---|
|
Enumerar elementos (foreach) | Todas: , , arrays |
|
Conjunto modificable (Add, Remove, Count) | , , |
|
Acceso por índice, modificación | , arrays |
|
Trabajar con pares "clave-valor" | , |
|
Conjunto (elementos únicos) | , |
P.D. Para simplificar, de momento ignoramos los “varios niveles” de herencia entre estas interfaces. Quién hereda de quién — eso lo veremos más adelante.
Visualización: árbol de interfaces de colecciones
IEnumerable<T>
^
|
ICollection<T>
^ ^
| |
IList<T> ISet<T>
| |
List<T> HashSet<T>
Para diccionarios:
IEnumerable<KeyValuePair<K,V>>
^
|
ICollection<KeyValuePair<K,V>>
^
|
IDictionary<K,V>
|
Dictionary<K,V>
3. ¿Para qué sirven las interfaces en la vida real?
Flexibilidad
Cuando tu método o clase trabaja no con un tipo concreto (List<int>), sino con una interfaz (IEnumerable<int>), puedes pasarle cualquier colección que implemente esa interfaz. Al final, tu programa es flexible y ampliable. (En vez de “Solo iPhone” — ahora vale cualquier móvil con Bluetooth.)
static void PrintAll(IEnumerable<string> collection)
{
foreach (var item in collection)
Console.WriteLine(item);
}
¡Ahora puedes imprimir los elementos de cualquier lista, array, conjunto, incluso el resultado de una consulta LINQ!
Unificación
Si implementas tu propio tipo de colección (por ejemplo, una colección muy específica para alguna tarea), implementa las interfaces necesarias — y tu colección funcionará con código ajeno que ni sabe de tu colección. Por eso en .NET se suele decir: “programa a nivel de interfaces, no de implementaciones concretas”.
Compatibilidad con herramientas estándar
Gracias al soporte de interfaces estándar, tus colecciones pueden funcionar con LINQ, ordenación, serialización y otras cosas de .NET.
4. List<T> — es lista, colección y enumeración a la vez
Unas palabras sobre enumeración
IEnumerable<T> — es la interfaz más importante y básica de colecciones en .NET. Define un solo método — GetEnumerator(), que devuelve un enumerador para iterar la colección. Gracias a eso puedes escribir:
foreach (var name in names)
{
Console.WriteLine(name);
}
Donde names puede ser lista, conjunto, incluso array. foreach es un azúcar sintáctico que por dentro usa el método GetEnumerator().
La interfaz IEnumerable (sin <T>) es más antigua, no genérica. Ahora casi siempre usa la versión genérica.
Sintaxis de declaración de la clase List
Mira cómo se declara la clase List<T>, si le quitas un poco de magia:
public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, ...
Eso significa que el objeto List<T> se puede usar como:
- Lista con acceso aleatorio por índice (IList<T>)
- Colección con posibilidad de añadir/eliminar elementos (ICollection<T>)
- Simplemente un conjunto de elementos por el que puedes iterar (IEnumerable<T>)
Ejemplo:
List<string> names = new List<string> { "Anna", "Boris", "Bill" };
// Como IEnumerable - puedes recorrer en un bucle
IEnumerable<string> asEnumerable = names;
foreach (var n in asEnumerable) Console.WriteLine(n);
// Como ICollection - puedes saber el Count:
ICollection<string> asCollection = names;
int count = asCollection.Count;
// Como IList - puedes acceder por índice:
IList<string> asList = names;
string firstItem = asList[0];
5. Ejemplo de aplicación
Supón que tenemos una app sencilla para gestionar usuarios y sus roles (empezamos a hacerla en lecciones anteriores):
// Clase de usuario
public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
Podemos guardar usuarios en cualquier colección que implemente IEnumerable<User>:
List<User> listUsers = new List<User> { new User { Name = "Anna", Age = 20 } };
HashSet<User> setUsers = new HashSet<User> { new User { Name = "Boris", Age = 25 } };
// Método que imprime los nombres de todos los usuarios
static void PrintUserNames(IEnumerable<User> users)
{
foreach (var user in users)
Console.WriteLine(user.Name);
}
// Usamos con distintos tipos de colecciones:
PrintUserNames(listUsers);
PrintUserNames(setUsers);
¡Ahora la app es mucho más flexible — sin magia ni sobrecargas personalizadas!
6. Errores típicos y trampas
Uno de los errores más comunes — usar tipos demasiado concretos cuando basta con una interfaz:
// Mal (te atas a la implementación)
void DoSomethingWithList(List<int> numbers) { ... }
// Mejor (el método funciona con cualquier colección)
void DoSomethingWithNumbers(IEnumerable<int> numbers) { ... }
Otra trampa — olvidar que la interfaz solo define las operaciones que describe explícitamente. Por ejemplo, en IEnumerable<T> no puedes saber el número de elementos (Count), para eso tienes que usar ICollection<T> — o contarlos tú mismo.
GO TO FULL VERSION