Aller au contenu

Les interfaces : IEquatable et IComparable

Les interfaces sont essentiellement des contrats pour s'assurer qu'une classe implémente une ou plusieurs méthodes / accesseurs.

Le standard au niveau du nom des interfaces est un I majuscule suivi du nom de l'interface (ex: IEquatable, IComparable).

Déclaration de base

Dans l'interface, on déclare uniquement les signatures de méthodes, se terminant par un point-virgule.

public interface IUneInterface
{  
    void Methode();
    int Calculer(int variable);
}

Les 4 cas d'implémentation essentiels

Pour assurer une compatibilité totale avec les collections et algorithmes .NET (tris, recherches), une classe métier doit gérer les 4 cas suivants.

1. IEquatable (Version générique)

Permet de définir l'égalité basée sur les données réelles (règles métiers).
- Règle : Retourne false si l'autre objet est null.

2. Override de Equals(object)

Redéfinit la méthode héritée de Object.
- Approche : Utiliser as pour le transtypage, puis is null pour vérifier.
- Action : Relance la méthode typée (Cas 1).

3. IComparable (Version générique)

Définit l'ordre de tri des objets entre eux.
- Règle : Retourne 1 si l'autre objet est null (on est considéré plus grand que "rien").
- Retour : 0 si égal, positif si plus grand, négatif si plus petit.

4. IComparable (Version object)

Version non-générique pour la compatibilité avec les anciennes structures.
- Approche : Utiliser as pour le transtypage, puis is null pour vérifier.
- Action : Relance la méthode typée (Cas 3).


Exemple d'implémentation complète

Voici une classe Etudiant respectant rigoureusement ces 4 cas ainsi que les conventions de nommage C#.

/// <summary>
/// Représente un étudiant pouvant être comparé par sa moyenne.
/// </summary>
public class Etudiant : IEquatable<Etudiant>, IComparable<Etudiant>, IComparable
{
    private string _nom;
    private double _moyenne;

    /// <summary>
    /// Initialise une nouvelle instance de la classe <see cref="Etudiant"/>.
    /// </summary>
    public Etudiant(string nom, double moyenne)
    {
        _nom = nom;
        _moyenne = moyenne;
    }

    // --- CAS 1 : IEquatable<Etudiant> (Générique) ---
    /// <summary>
    /// Vérifie l'égalité basée sur les données membres.
    /// </summary>
    public bool Equals(Etudiant other)
    {
        // Utilisation de "is null"
        if (other is null) 
        {
            return false;
        }

        return _nom == other._nom && _moyenne == other._moyenne;
    }

    // --- CAS 2 : Override de Equals(object) ---
    /// <summary>
    /// Relance la version typée après transtypage.
    /// </summary>
    public override bool Equals(object obj)
    {
        // Approche "as" demandée : c'est un "3 dans 1"  
        // 1. On transtype  
        // 2. On a null si ce n'est pas le bon type  
        // 3. Si obj est déjà null, temp reste null  
        Etudiant temp = obj as Etudiant;  

        // Utilisation de "is" pour le check de nullité
        if (temp is null) 
        {
            return false;
        }

        // Relance la méthode typée
        return Equals(temp);
    }

    // --- CAS 3 : IComparable<Etudiant> (Générique) ---
    /// <summary>
    /// Compare la moyenne pour le tri.
    /// </summary>
    public int CompareTo(Etudiant other)
    {
        // Nous sommes plus grand que null
        if (other is null) 
        {
            return 1;
        }

        return _moyenne.CompareTo(other._moyenne);
    }

    // --- CAS 4 : IComparable (Object) ---
    /// <summary>
    /// Relance la version typée après transtypage.
    /// </summary>
    public int CompareTo(object obj)
    {
        // Approche "as" demandée : c'est un "3 dans 1"  
        // 1. On transtype  
        // 2. On a null si ce n'est pas le bon type  
        // 3. Si obj est déjà null, temp reste null  
        Etudiant temp = obj as Etudiant;  

        // Utilisation de "is" pour le check de nullité
        if (temp is null) 
        {
            return 1;
        }

        // Relance la méthode typée
        return CompareTo(temp);
    }

    /// <summary>
    /// Requis lors de la redéfinition de Equals.
    /// </summary>
    public override int GetHashCode() => HashCode.Combine(_nom, _moyenne);
}

Note sur StringComparison.Ordinal

Lorsqu'on compare des chaînes de caractères (ex: Nom), il est recommandé d'utiliser StringComparison.Ordinal pour une comparaison binaire stricte.

Critère StringComparison.Ordinal Autres (CurrentCulture, etc.)
Vitesse ✅ Très rapide (binaire) ❌ Plus lent
Prédictibilité ✅ Identique partout ❌ Varie selon la langue
Utilisation Tris techniques, IDs, clés Affichage utilisateur
// Exemple d'utilisation dans un CompareTo
return string.Compare(_nom, other._nom, StringComparison.Ordinal);