1. Clasificación de interfaces clave
Si piensas que las interfaces son solo un juguete para arquitectos y "código limpio", y que en la vida real puedes pasar sin ellas, déjame sorprenderte: cualquier proyecto serio en C# se mete de lleno en interfaces. ¿Por qué? ¡Porque casi cada parte de la biblioteca estándar de .NET está montada sobre interfaces! Sin ellas no puedes trabajar con colecciones, ni leer archivos, ni siquiera filtrar colecciones con LINQ.
Las interfaces son la base del polimorfismo y la extensibilidad en .NET, y son las que definen cómo todos los "bloques" del framework encajan entre sí.
La biblioteca estándar de .NET está literalmente llena de interfaces. Para no ahogarnos en este mar, te propongo la siguiente clasificación (obvio, no es completa — ¡no hay vida suficiente para abarcar todo!):
| Categoría | Interfaces | ¿Para qué sirven? |
|---|---|---|
| Colecciones | , , , , |
Recorrer, modificar, acceso por índice, trabajar con pares clave-valor |
| Manejo de recursos | |
Liberar recursos (archivos, conexiones, streams) |
| Comparación | , , |
Ordenar, comparar, unicidad de objetos |
| Serialización | |
Convertir objetos en un stream de bytes (y viceversa) |
| LINQ y consultas | , |
Soporte para consultas complejas (por ejemplo, a BD) |
| Asincronía | , |
Recorrido y limpieza de recursos asíncronos |
| Eventos y notificaciones | , |
Reaccionar a cambios en propiedades/colecciones |
| Fecha y hora | |
Formateo personalizado de strings |
| Estructuras de datos | , |
Comparación profunda de colecciones y tuplas |
| Streams/entrada-salida | , , , |
Trabajar con streams, notificaciones push/pull |
¡Ojo! Muchos de los interfaces de arriba tienen tipos genéricos: List<string>. Normalmente se usan en colecciones y para interfaces de colecciones Int[] → List<int>. Los veremos más a fondo en unos niveles, cuando toquemos colecciones.
2. Interfaces de colecciones
IEnumerable y IEnumerator — tu pase para foreach
Prácticamente cualquier colección en .NET implementa la interfaz IEnumerable o incluso su versión genérica IEnumerable<T>. Es esta interfaz la que te deja usar la magia de foreach:
List<int> numbers = new List<int> { 1, 2, 3 };
foreach (int n in numbers) // funciona porque List<int> implementa IEnumerable<int>
{
Console.WriteLine(n);
}
Así se ve esta interfaz en su versión minimalista:
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
IEnumerator es la interfaz del propio "recorredor", el que se encarga de moverse por tu colección:
public interface IEnumerator
{
bool MoveNext();
object Current { get; }
void Reset();
}
En tareas reales muy a menudo vas a pasar parámetros y devolver valores del tipo IEnumerable<T>. Por ejemplo, un método que devuelve todos los números pares de un array:
public IEnumerable<int> GetEvenNumbers(int[] array)
{
foreach (var x in array)
if (x % 2 == 0) yield return x;
}
Sí, la palabra clave yield hace magia — implementa la interfaz por ti, pero de eso hablamos luego.
ICollection y IList — colecciones con acceso por índice y modificación
Si necesitas no solo recorrer, sino también añadir/quitar elementos o incluso acceder por índice, necesitas interfaces más especializadas:
- ICollection<T> — añade métodos Add, Remove, y la propiedad Count.
- IList<T> — amplía las operaciones posibles, incluyendo acceso por índice (this[int index]).
public void PrintFirstItem(IList<string> list)
{
if (list.Count > 0)
Console.WriteLine(list[0]);
}
List<T> implementa IEnumerable<T>, ICollection<T> y IList<T>. Súper cómodo — puedes tratarlo como una lista simple o usar todos sus métodos potentes.
IDictionary<TKey, TValue> — pares "clave-valor"
Si te mola trabajar con diccionarios (¡y pasa muy a menudo!), usa la interfaz IDictionary<TKey, TValue>. Te garantiza que puedes obtener el valor por clave y viceversa.
public void PrintAllPairs(IDictionary<string, int> ages)
{
foreach (var pair in ages)
Console.WriteLine($"{pair.Key}: {pair.Value}");
}
Aquí ages puede ser lo que sea, desde Dictionary<string, int> hasta un SortedDictionary<string, int> — lo importante es que implementan esta interfaz.
3. Interfaz IDisposable: manejo correcto de recursos
Todo el input/output, manejo de archivos, conexiones de red, bases de datos en .NET gira en torno a la interfaz IDisposable. Esta interfaz define un contrato vital: si un objeto tiene recursos no gestionados, hay que "limpiarlo" después de usarlo. Sí, es el que hizo posible el sintaxis using:
using (StreamReader reader = new StreamReader("file.txt"))
{
// Trabajamos con el archivo, ¡y después de using se cierra seguro!
string line = reader.ReadLine();
}
¿Cómo es la interfaz? Ridículamente simple:
public interface IDisposable
{
void Dispose();
}
¡Pero cualquier librería "seria" la implementa! Más sobre buenas prácticas con esta interfaz en la documentación oficial.
4. Interfaces para comparación y unicidad
IComparable<T> y IComparer<T>
Si quieres que tu colección de objetos se pueda ordenar, los objetos deben poder compararse entre sí. Para eso existe la interfaz IComparable<T>:
public class Student : IComparable<Student>
{
public string Name { get; set; }
public int Score { get; set; }
public int CompareTo(Student? other)
{
// Ordenar por puntuación descendente
if (other == null) return 1;
return other.Score.CompareTo(this.Score);
}
}
// Ahora puedes ordenar estudiantes:
var students = new List<Student> { ... };
students.Sort();
Más info en la documentación de Microsoft.
IComparer<T> te deja definir la comparación fuera de la clase — por ejemplo, a veces quieres ordenar estudiantes por nombre, otras por puntuación:
public class NameComparer : IComparer<Student>
{
public int Compare(Student? x, Student? y)
{
return string.Compare(x?.Name, y?.Name);
}
}
IEquatable<T> — comparación de igualdad
¿Quieres que tu objeto funcione en un HashSet<T> (o sea, que sea "único" en colecciones)? Implementa IEquatable<T>:
public class Person : IEquatable<Person>
{
public string Name { get; set; }
public bool Equals(Person? other)
{
if (other == null) return false;
return this.Name == other.Name;
}
}
5. Interfaces para eventos y notificaciones
¿Alguna vez has querido que tu programa "se entere" de que una propiedad de un objeto ha cambiado? Por ejemplo, quieres que la interfaz se actualice sola cuando el usuario cambia el apellido. Para eso existe la interfaz INotifyPropertyChanged. Es súper popular en apps con interfaz gráfica (como WPF o Xamarin).
La firma de la interfaz es sencilla:
public interface INotifyPropertyChanged
{
event PropertyChangedEventHandler? PropertyChanged;
}
Ejemplo de implementación:
public class User : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
private string name;
public string Name
{
get => name;
set
{
if (name != value)
{
name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
}
}
Ahora cualquier binding de la interfaz a este objeto se actualizará automáticamente al cambiar Name. Si te interesa, échale un ojo a la documentación oficial.
6. Otras interfaces útiles
Interfaz IFormattable: formateo flexible
Cuando quieres que tu objeto se pueda mostrar bonito y variado en string (por ejemplo, con diferente número de decimales), implementa la interfaz IFormattable:
public class Temperature : IFormattable
{
public double Celsius { get; }
public Temperature(double celsius) => Celsius = celsius;
public string ToString(string? format, IFormatProvider? formatProvider)
{
// Sin complicaciones, mostramos 'C' o 'F' según el formato
if (format == "F")
return $"{Celsius * 9 / 5 + 32} F";
return $"{Celsius} C";
}
}
Ahora puedes llamar a temp.ToString("F", null) o temp.ToString("C", null). Por eso DateTime, double, decimal y otros tipos soportan formateo flexible.
Interfaces para asincronía
Con la llegada de la asincronía en C# hicieron falta nuevas interfaces para el mundo async. Por ejemplo, si tu objeto libera recursos de forma asíncrona — implementa IAsyncDisposable:
public interface IAsyncDisposable
{
ValueTask DisposeAsync();
}
¡Y ahora en vez de using escribes await using!
Con enumeraciones asíncronas (await foreach) funciona la interfaz IAsyncEnumerable<T>, que te deja recorrer elementos sin bloquear el hilo principal. Se usa, por ejemplo, al leer archivos grandes por partes o al trabajar con streams de datos de internet. Más info en el nivel 58 :P
Interfaces de serialización: ISerializable
Si tu tarea es convertir objetos en un stream de bytes para guardar/enviar (por ejemplo, por red), .NET te da la interfaz ISerializable. Hoy no se usa tanto porque hay mecanismos más cómodos (por atributos y serializadores ya hechos), pero merece mención. Ejemplo de firma:
public interface ISerializable
{
void GetObjectData(SerializationInfo info, StreamingContext context);
}
7. Uso de interfaces en la práctica: app real
Supón que te curras una app de consola para gestionar libros en una biblioteca (oye, ¡alguien tiene que hacerlo!). Quieres poder mostrar listas de libros, ordenarlos, filtrarlos, cargar y guardar datos. Gracias a saber interfaces de .NET puedes:
- Devolver distintos tipos de colecciones usando IEnumerable<Book>, para que el usuario no tenga que pensar si es array o lista.
- Añadir ordenación por distintos criterios: implementas IComparable<Book> para ordenar por título y un IComparer<Book> aparte para autor.
- Asegurar liberación eficiente de recursos al trabajar con archivos usando IDisposable.
- Filtrar libros por género o autor con LINQ, que funciona con todo lo que implemente IEnumerable<T>.
- Escalar la app — por ejemplo, cambiar almacenamiento de archivos por base de datos, si tus clases trabajan con interfaces (IBookStorage) y no implementaciones concretas.
8. Errores típicos y particularidades
Uno de los errores más comunes de novato es usar implementaciones concretas ("List") en vez de interfaces ("IEnumerable", "IList") al declarar variables y argumentos de métodos. RECUERDA: si "programas con interfaces", tu código será flexible y fácil de ampliar.
A veces hay que tener ojo al elegir qué nivel de interfaz usar — por ejemplo, si escribes un método que solo necesita recorrer, usa IEnumerable<T> y no IList<T>, para no imponer restricciones innecesarias.
Otra cosa: si tu clase tiene muchas interfaces y entre ellas hay métodos o propiedades que se llaman igual, tendrás que usar implementación explícita de interfaz (explicit implementation), si no el compilador se lía.
GO TO FULL VERSION