1. Classification des interfaces clés
Si tu penses que les interfaces c’est juste un jouet pour les architectes et les fans de “clean code”, et qu’en vrai on peut s’en passer, laisse-moi te surprendre : tout projet sérieux en C# finit par plonger à fond dans les interfaces. Pourquoi ? Parce que quasiment chaque morceau de la bibliothèque standard .NET est construit autour des interfaces ! Sans elles, tu ne peux ni manipuler des collections, ni lire des fichiers, ni même filtrer des collections avec LINQ.
Les interfaces, c’est le socle du polymorphisme et de l’extensibilité dans .NET, et c’est elles qui font que tous les “blocs” du framework s’assemblent ensemble.
La bibliothèque standard .NET est littéralement blindée d’interfaces. Pour pas te noyer là-dedans, je te propose cette petite classification (évidemment, elle est pas complète — faudrait plusieurs vies pour tout couvrir !) :
| Catégorie | Interfaces | À quoi ça sert ? |
|---|---|---|
| Collections | , , , , |
Itérer, modifier, accès par index, gestion des paires clé-valeur |
| Gestion des ressources | |
Libérer les ressources (fichiers, connexions, streams) |
| Comparaison | , , |
Tri, comparaison, unicité des objets |
| Sérialisation | |
Transformer des objets en flux de bytes (et inversement) |
| LINQ et requêtes | , |
Support des requêtes complexes (genre vers une BDD) |
| Async | , |
Itération et nettoyage de ressources en asynchrone |
| Events et notifications | , |
Réagir aux changements de propriétés/collections |
| Date et heure | |
Formatage custom de chaînes |
| Structures de données | , |
Comparaison profonde de collections et tuples |
| Streams/I-O | , , , |
Travailler avec les streams, notifications push/pull |
Important ! Beaucoup des interfaces ci-dessus ont des types paramétrés : List<string>. D’habitude, on les utilise dans les collections et pour les interfaces de collections Int[] → List<int>. On verra ça plus en détail dans quelques niveaux, quand on attaquera les collections.
2. Interfaces de collections
IEnumerable et IEnumerator — ton pass pour foreach
Quasiment chaque collection en .NET implémente l’interface IEnumerable ou même sa version générique IEnumerable<T>. C’est cette interface qui te permet d’utiliser la magie du foreach :
List<int> numbers = new List<int> { 1, 2, 3 };
foreach (int n in numbers) // ça marche parce que List<int> implémente IEnumerable<int>
{
Console.WriteLine(n);
}
Voilà à quoi ressemble cette interface en version minimaliste :
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
IEnumerator — c’est l’interface du “parcourreur” qui se balade dans ta collection :
public interface IEnumerator
{
bool MoveNext();
object Current { get; }
void Reset();
}
Dans la vraie vie, tu vas très souvent passer des paramètres et retourner des valeurs du type IEnumerable<T>. Par exemple, une méthode qui retourne tous les nombres pairs d’un tableau :
public IEnumerable<int> GetEvenNumbers(int[] array)
{
foreach (var x in array)
if (x % 2 == 0) yield return x;
}
Ouais, le mot-clé yield fait de la magie — il implémente l’interface pour toi, mais on en reparlera plus tard.
ICollection et IList — collections avec accès par index et modif
Si tu veux pas juste itérer, mais aussi ajouter/enlever des éléments ou même les choper par index, il te faut des interfaces plus spécialisées :
- ICollection<T> — ajoute les méthodes Add, Remove, et la propriété Count.
- IList<T> — étend la liste des opérations possibles, y compris l’accès par index (this[int index]).
public void PrintFirstItem(IList<string> list)
{
if (list.Count > 0)
Console.WriteLine(list[0]);
}
List<T> implémente à la fois IEnumerable<T>, ICollection<T> et IList<T>. Super pratique — tu peux bosser avec comme simple liste énumérable ou profiter de tous les methods cools.
IDictionary<TKey, TValue> — paires “clé-valeur”
Si tu kiffes bosser avec des dicos (et crois-moi, ça arrive souvent !), utilise l’interface IDictionary<TKey, TValue>. Elle garantit que tu peux choper une valeur par sa clé et vice versa.
public void PrintAllPairs(IDictionary<string, int> ages)
{
foreach (var pair in ages)
Console.WriteLine($"{pair.Key}: {pair.Value}");
}
Ici, ages peut être n’importe quoi, genre Dictionary<string, int> ou même SortedDictionary<string, int> — l’important c’est qu’ils implémentent cette interface !
3. Interface IDisposable : gérer les ressources comme un pro
Tout ce qui est I/O, gestion de fichiers, connexions réseau, bases de données en .NET tourne autour de l’interface IDisposable. Cette interface définit un contrat vital : si un objet a des ressources non managées, il faut le “nettoyer” après usage. Ouais, c’est ce gars-là qui rend possible le mot-clé using :
using (StreamReader reader = new StreamReader("file.txt"))
{
// On bosse avec le fichier, et après le using le fichier est garanti fermé !
string line = reader.ReadLine();
}
Comment est foutue l’interface ? Trop simple :
public interface IDisposable
{
void Dispose();
}
Mais chaque “grosse” bibliothèque l’implémente ! Pour plus de tips sur les bonnes pratiques avec cette interface, checke la doc officielle.
4. Interfaces pour la comparaison et l’unicité
IComparable<T> et IComparer<T>
Si tu veux que ta collection d’objets puisse être triée, tes objets doivent savoir se comparer entre eux. C’est pour ça qu’il existe l’interface IComparable<T> :
public class Student : IComparable<Student>
{
public string Name { get; set; }
public int Score { get; set; }
public int CompareTo(Student? other)
{
// Tri décroissant par score
if (other == null) return 1;
return other.Score.CompareTo(this.Score);
}
}
// Maintenant tu peux trier les étudiants :
var students = new List<Student> { ... };
students.Sort();
Plus de détails dans la doc Microsoft.
IComparer<T> permet de définir la comparaison en dehors de la classe — genre tu veux parfois trier les étudiants par nom, parfois par score :
public class NameComparer : IComparer<Student>
{
public int Compare(Student? x, Student? y)
{
return string.Compare(x?.Name, y?.Name);
}
}
IEquatable<T> — comparaison d’égalité
Tu veux que ton objet soit dans un HashSet<T> (c’est-à-dire “unique” pour les collections) ? Implémente 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 pour les events et notifications
T’as déjà voulu que ton appli “sache” quand un objet change de propriété ? Genre tu veux que l’UI se mette à jour toute seule quand l’utilisateur change son nom ? C’est pour ça qu’il existe l’interface INotifyPropertyChanged. Elle est super populaire dans les applis avec UI graphique (genre WPF ou Xamarin).
La signature de l’interface est simple :
public interface INotifyPropertyChanged
{
event PropertyChangedEventHandler? PropertyChanged;
}
Exemple d’implémentation :
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)));
}
}
}
}
Maintenant, tous les bindings de l’UI à cet objet vont se mettre à jour automatiquement quand Name change. Si ça t’intéresse, lis la doc officielle.
6. Autres interfaces utiles
Interface IFormattable : formatage flexible
Quand tu veux que ton objet puisse s’afficher joliment et de façon variée en string (genre avec un nombre de décimales différent), implémente l’interface IFormattable :
public class Temperature : IFormattable
{
public double Celsius { get; }
public Temperature(double celsius) => Celsius = celsius;
public string ToString(string? format, IFormatProvider? formatProvider)
{
// On va pas compliquer, juste afficher 'C' ou 'F' selon le format
if (format == "F")
return $"{Celsius * 9 / 5 + 32} F";
return $"{Celsius} C";
}
}
Maintenant tu peux appeler temp.ToString("F", null) ou temp.ToString("C", null). C’est pour ça que DateTime, double, decimal et d’autres types intégrés supportent le formatage flexible.
Interfaces pour l’async
Avec l’arrivée de l’async en C#, il a fallu de nouvelles interfaces pour bosser dans ce monde asynchrone. Par exemple, si ton objet libère des ressources en async — implémente IAsyncDisposable :
public interface IAsyncDisposable
{
ValueTask DisposeAsync();
}
Et maintenant, au lieu de using tu écris await using !
Pour les énumérations asynchrones (await foreach), c’est l’interface IAsyncEnumerable<T> qui gère, ce qui permet d’itérer sans bloquer le thread principal. On l’utilise par exemple pour lire de gros fichiers par morceaux ou bosser avec des streams de données du net. Plus de détails au niveau 58 :P
Interfaces de sérialisation : ISerializable
Si ton but c’est de transformer des objets en flux de bytes pour sauvegarder/envoyer (genre sur le réseau), .NET propose l’interface ISerializable. On l’utilise moins aujourd’hui, vu qu’il y a des trucs plus pratiques (genre avec des attributs et des sérializers tout faits), mais ça mérite d’être cité. Voilà la signature :
public interface ISerializable
{
void GetObjectData(SerializationInfo info, StreamingContext context);
}
7. Utiliser les interfaces en vrai : appli concrète
Imaginons que tu codes une appli console pour gérer les livres d’une bibliothèque (ben ouais, faut bien que quelqu’un le fasse !). Tu veux pouvoir afficher des listes de livres, les trier, les filtrer, charger et sauvegarder des données. Grâce à ta maîtrise des interfaces .NET tu vas pouvoir :
- Retourner différents types de collections via IEnumerable<Book>, comme ça l’utilisateur se prend pas la tête à savoir si c’est un array ou une liste.
- Ajouter du tri selon plusieurs critères : tu implémentes IComparable<Book> pour trier par titre et un IComparer<Book> séparé pour l’auteur.
- Assurer la libération efficace des ressources quand tu bosses avec des fichiers grâce à IDisposable.
- Organiser le filtrage des livres par genre ou auteur avec LINQ, qui marche avec tout ce qui implémente IEnumerable<T>.
- Scaler l’appli — genre remplacer le stockage fichier par une BDD, si tes classes bossent via des interfaces (IBookStorage) et pas des implémentations concrètes.
8. Erreurs classiques et subtilités
Une des erreurs les plus courantes chez les débutants — utiliser des implémentations concrètes (“List”) au lieu des interfaces (“IEnumerable”, “IList”) quand tu déclares des variables ou des arguments de méthodes. RAPPEL : si tu “codes sur les interfaces”, ton code sera flexible et facile à faire évoluer.
Parfois, faut faire gaffe au niveau d’interface à utiliser — par exemple, si tu écris une méthode qui a juste besoin d’itérer, utilise IEnumerable<T> et pas IList<T>, pour pas imposer de contraintes inutiles.
Autre subtilité : si ta classe a plein d’interfaces, et qu’il y a des méthodes ou propriétés qui se recoupent, faudra utiliser l’implémentation explicite d’interface (explicit implementation), sinon le compilateur va râler.
GO TO FULL VERSION