mercredi 8 octobre 2025

Q/A Equals et GetHashCode



🔹 1. Quelle est la différence entre Equals() et == en C# ?

== :

  • Par défaut, pour les types référence, compare les références mémoire (identité des objets).

  • Pour les types valeur (structs), compare les valeurs champ par champ.

  • Peut être surchargé (operator overloading) pour changer le comportement.

Equals() :

  • Méthode virtuelle héritée de System.Object.

  • Sert à comparer le contenu logique (valeur) d’un objet.

  • Peut être redéfinie (override) pour personnaliser la logique d’égalité.

Exemple :

string s1 = new string("Hello".ToCharArray());
string s2 = new string("Hello".ToCharArray());

Console.WriteLine(s1 == s2);      // True -> opérateur surchargé dans System.String
Console.WriteLine(s1.Equals(s2)); // True -> compare le contenu
Console.WriteLine(ReferenceEquals(s1, s2)); // False -> pas la même instance

🧠 Résumé :

== → peut comparer références ou valeurs selon le type.
Equals() → compare le contenu logique (souvent redéfini).
ReferenceEquals() → compare les adresses mémoire uniquement.


🔹 2. Pourquoi doit-on redéfinir GetHashCode() lorsqu'on redéfinit Equals() ?

Parce que les deux méthodes doivent rester cohérentes entre elles.

✅ Contrat fondamental (défini par .NET) :

  1. Si a.Equals(b) est true, alors a.GetHashCode() doit être égal à b.GetHashCode().

  2. Si a.Equals(b) est false, leurs hashcodes peuvent être différents (mais pas obligatoire).

  3. Les hashcodes sont utilisés dans les collections basées sur le hachage (Dictionary, HashSet, etc.).

Exemple :

var set = new HashSet<Person>();
set.Add(new Person("John", 30));
set.Add(new Person("John", 30)); // doublon ? dépend de Equals/GetHashCode

➡️ Si GetHashCode() n’est pas redéfini, ces deux objets auront des hashcodes différents et seront traités comme distincts, même si Equals retourne true.

🧠 En résumé :

Toujours redéfinir GetHashCode() dès que Equals() est redéfini.


🔹 3. Donnez un exemple de redéfinition correcte des méthodes Equals() et GetHashCode().

Exemple complet :

public class Person
{
    public string Name { get; }
    public int Age { get; }

    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }

    public override bool Equals(object obj)
    {
        if (obj is Person other)
            return Name == other.Name && Age == other.Age;

        return false;
    }

    public override int GetHashCode()
    {
        // Combiner les hashcodes des propriétés significatives
        return HashCode.Combine(Name, Age);
    }

    public override string ToString() => $"{Name} ({Age})";
}

Utilisation :

var p1 = new Person("Alice", 25);
var p2 = new Person("Alice", 25);

Console.WriteLine(p1.Equals(p2)); // True
Console.WriteLine(p1.GetHashCode() == p2.GetHashCode()); // True

✅ Ici :

  • Equals() compare la logique métier (nom + âge).

  • GetHashCode() garantit la cohérence pour les collections de hachage.


🔹 4. Comment comparer des objets en tenant compte de leurs références et de leurs valeurs ?

Objectif Méthode à utiliser Description
Comparer les références ReferenceEquals(a, b) Vérifie si les deux variables pointent vers la même instance.
Comparer les valeurs a.Equals(b) ou == (si surchargé) Vérifie si les objets sont logiquement équivalents.

Exemple :

var a = new Person("Bob", 40);
var b = new Person("Bob", 40);
var c = a;

Console.WriteLine(ReferenceEquals(a, b)); // False (instances différentes)
Console.WriteLine(ReferenceEquals(a, c)); // True  (même instance)
Console.WriteLine(a.Equals(b));           // True  (même contenu)

🧠 En résumé :

  • ReferenceEquals → identité.

  • Equals → égalité logique.

  • == → selon implémentation.


🔹 5. Pourquoi String.Equals est-il préféré à == pour la comparaison de chaînes ?

Même si == est surchargé dans System.String pour comparer les valeurs, String.Equals offre :

  1. Plus de contrôle via des options (StringComparison).

  2. Une meilleure lisibilité et robustesse dans les comparaisons culturelles.

  3. Moins d’ambiguïté (évite les comportements inattendus dans certaines cultures ou localisations).

Exemple :

string s1 = "résumé";
string s2 = "RESUMÉ";

// Comparaison insensible à la casse et à la culture
bool equal = string.Equals(s1, s2, StringComparison.OrdinalIgnoreCase);
Console.WriteLine(equal); // True

🧠 Résumé :

Utilisez String.Equals(a, b, StringComparison.OrdinalIgnoreCase) pour une comparaison explicite et fiable, surtout dans les applications globalisées.


🔹 6. Qu'est-ce que l’interface IEquatable<T> et pourquoi est-elle utile ?

✅ Définition :

IEquatable<T> fournit une méthode typée Equals(T other) pour comparer des objets sans passer par object.

public interface IEquatable<T>
{
    bool Equals(T other);
}

✅ Avantages :

  • Performance : évite le boxing/unboxing (notamment pour les structs).

  • Cohérence : utilisée par les collections génériques (List<T>.Contains, Dictionary, etc.).

  • Type safety : pas besoin de caster en object.

Exemple :

public class Person : IEquatable<Person>
{
    public string Name { get; set; }

    public bool Equals(Person other)
    {
        if (other is null) return false;
        return Name == other.Name;
    }

    public override bool Equals(object obj) => Equals(obj as Person);
    public override int GetHashCode() => Name?.GetHashCode() ?? 0;
}

Utilisation :

var p1 = new Person { Name = "Tom" };
var p2 = new Person { Name = "Tom" };
Console.WriteLine(p1.Equals(p2)); // True

🧠 En résumé :

Implémentez IEquatable<T> pour des comparaisons rapides, typées et cohérentes dans les collections génériques.


📘 Récapitulatif synthétique

Concept Description Exemple clé
== Compare références ou valeurs selon le type a == b
Equals() Compare la valeur logique a.Equals(b)
GetHashCode() Doit être cohérent avec Equals() HashCode.Combine(...)
ReferenceEquals() Compare les références mémoire ReferenceEquals(a, b)
String.Equals() Comparaison de chaînes robuste String.Equals(a, b, StringComparison.Ordinal)
IEquatable<T> Comparaison typée et performante class MyType : IEquatable<MyType>




🔹 1. Quelle est la différence entre deep copy et shallow copy en C# ?

Shallow Copy (copie superficielle)

  • Copie uniquement les champs de premier niveau (valeurs primitives).

  • Les objets référencés sont partagés entre la source et la copie → ils pointent vers la même instance.

class Person
{
    public string Name;
    public Address Addr;
    public Person ShallowCopy() => (Person)this.MemberwiseClone();
}

class Address { public string City; }

var p1 = new Person { Name = "John", Addr = new Address { City = "Paris" } };
var p2 = p1.ShallowCopy();
p2.Addr.City = "Lyon"; // ❌ Modifie aussi p1.Addr.City (référence partagée)

Deep Copy (copie profonde)

  • Crée une nouvelle instance complète, y compris pour les objets imbriqués.

  • Les modifications sur la copie n’affectent pas l’original.

public Person DeepCopy() =>
    new Person
    {
        Name = this.Name,
        Addr = new Address { City = this.Addr.City }
    };

🧠 Résumé :

Shallow = copie d’adresse,
Deep = duplication complète d’objet.


🔹 2. Comment implémenter correctement IComparable dans une classe personnalisée ?

✅ But :

Permet de définir un ordre naturel pour vos objets (tri, recherche, etc.).

🧩 Exemple :

public class Employee : IComparable<Employee>
{
    public int Id { get; set; }
    public string Name { get; set; }

    public int CompareTo(Employee other)
    {
        if (other == null) return 1;
        return this.Id.CompareTo(other.Id); // Tri par Id
    }
}

🧠 Bonnes pratiques :

  • Retourner :

    • 0 si égalité

    • > 0 si l’objet courant est “plus grand”

    • < 0 sinon

  • Compatible avec List<T>.Sort(), Array.Sort() et LINQ OrderBy.


🔹 3. Pourquoi utiliser StringComparison.OrdinalIgnoreCase pour comparer des chaînes de caractères ?

✅ Objectif :

Comparer deux chaînes sans tenir compte de la casse (a == A) et de manière performante.

🧩 Exemple :

bool result = string.Equals("HELLO", "hello", StringComparison.OrdinalIgnoreCase); // true

⚙️ Comparaisons possibles :

  • Ordinal → compare les valeurs Unicode, rapide et déterministe.

  • CurrentCultureIgnoreCase → sensible à la culture locale (moins performant, plus complexe).

🧠 Résumé :

Utiliser OrdinalIgnoreCase pour les comparaisons techniques (fichiers, clés, IDs),
CurrentCultureIgnoreCase pour le texte destiné à l’utilisateur.


🔹 4. Comment fonctionne l’opérateur ?? (null-coalescing) et ?. (null-conditional) ?

?? — Null-coalescing

Retourne la valeur de droite si celle de gauche est null :

string name = user.Name ?? "Inconnu";

?. — Null-conditional

Évite les exceptions NullReferenceException :

string city = user?.Address?.City;

🧠 Résumé :

  • ?. : arrêt sûr si null (propage null)

  • ?? : fournit une valeur par défaut.


🔹 5. Quelle est la différence entre ReferenceEquals() et Equals() ?

Méthode Description Utilisation
ReferenceEquals(a, b) Compare les adresses mémoire Teste si les deux références pointent vers le même objet
Equals(a, b) Compare les valeurs si redéfinie Peut être surchargée pour tester l’égalité logique

🧩 Exemple :

var p1 = new Person { Name = "John" };
var p2 = new Person { Name = "John" };

Console.WriteLine(ReferenceEquals(p1, p2)); // false
Console.WriteLine(p1.Equals(p2));            // true si Equals est redéfini

🧠 Résumé :

ReferenceEquals = même instance
Equals = mêmes valeurs (logiquement équivalentes)


🔹 6. Pourquoi faut-il utiliser StringBuilder au lieu de string pour les manipulations de texte intensives ?

✅ Caractéristique de string :

  • string est immutable → chaque concaténation crée un nouvel objet.

  • En boucle, cela génère des allocations mémoire coûteuses.

StringBuilder :

  • Mutable, construit la chaîne en mémoire sans recréer des objets à chaque étape.

🧩 Exemple :

var sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
    sb.Append("Hello");

string result = sb.ToString();

🧠 Résumé :

StringBuilder = plus performant pour concaténations répétées ou gros volumes de texte.


🔹 7. Quelle est la différence entre object.Equals(a, b) et a.Equals(b) ?

Méthode Description Avantage
object.Equals(a, b) Gère automatiquement les cas où a ou b sont null Évite NullReferenceException
a.Equals(b) Appelle la méthode Equals de l’objet a Nécessite que a ne soit pas null

🧩 Exemple :

object a = null;
object b = null;

Console.WriteLine(object.Equals(a, b)); // ✅ true
Console.WriteLine(a.Equals(b));          // ❌ NullReferenceException

🧠 Résumé :

Toujours préférer object.Equals(a, b) si on n’est pas sûr que a soit non-null.


🔹 8. Pourquoi l’interface IEqualityComparer est-elle utile et comment l’implémenter ?

✅ Objectif :

Permet de personnaliser la logique de comparaison et de hachage d’objets dans les collections (Dictionary, HashSet…).

🧩 Exemple :

Comparer des personnes sans tenir compte de la casse :

public class PersonComparer : IEqualityComparer<Person>
{
    public bool Equals(Person x, Person y) =>
        string.Equals(x?.Name, y?.Name, StringComparison.OrdinalIgnoreCase);

    public int GetHashCode(Person obj) =>
        obj.Name?.ToLowerInvariant().GetHashCode() ?? 0;
}

var people = new HashSet<Person>(new PersonComparer());

🧠 Résumé :

IEqualityComparer<T> permet d’avoir plusieurs stratégies d’égalité pour une même classe.


🔹 9. Comment empêcher la modification d’un objet après son instanciation (pattern immuable) ?

✅ But :

Créer des objets immutables (non modifiables après création).
→ Sécurité, thread-safety, prévisibilité.

🧩 Exemple :

public class User
{
    public string Name { get; }
    public int Age { get; }

    public User(string name, int age)
    {
        Name = name;
        Age = age;
    }

    public User WithAge(int newAge) => new User(Name, newAge);
}

🧠 Bonnes pratiques :

  • Utiliser des propriétés en lecture seule

  • Pas de setters publics

  • Fournir des méthodes “WithX()” pour créer de nouvelles versions


🔹 10. Comment comparer deux collections d’objets en C# ?

✅ Plusieurs options :

🔸 a) Comparaison séquentielle (ordre important)

bool equal = list1.SequenceEqual(list2);

🔸 b) Comparaison sans ordre :

bool equal = list1.OrderBy(x => x).SequenceEqual(list2.OrderBy(x => x));

🔸 c) Comparaison avec logique personnalisée :

bool equal = list1.SequenceEqual(list2, new PersonComparer());

🧠 Résumé :

  • SequenceEqual() → parfait pour comparer des séquences

  • IEqualityComparer<T> → utile pour définir les critères de comparaison personnalisés.


📘 RÉCAPITULATIF SYNTHÉTIQUE

Thème Objectif Exemple clé
Deep vs Shallow Copy Contrôle du clonage d’objets MemberwiseClone() vs duplication complète
IComparable Tri naturel d’objets CompareTo()
StringComparison Comparaison optimisée de chaînes OrdinalIgnoreCase
?? et ?. Gestion du null élégante user?.Name ?? "N/A"
ReferenceEquals vs Equals Référence vs Valeur ReferenceEquals(a,b)
StringBuilder Performance pour concaténation sb.Append()
object.Equals vs a.Equals Gestion du null object.Equals(a,b)
IEqualityComparer Comparaison personnalisée HashSet<Person>(new PersonComparer())
Objet immuable Sécurité et thread-safety readonly properties
Comparer collections Égalité de séquences SequenceEqual()


Aucun commentaire:

Enregistrer un commentaire