Deep cloning en C# : de la réflexion à la sérialisation

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Copie profonde en C# : pourquoi c'est un casse-tête pour chaque développeur ?

\n\n

Avez-vous déjà copié un objet en C#, pour découvrir avec surprise que la modification de la "copie" casse l'original ? Familier ? C'est le problème classique de la copie superficielle (shallow copy). Lorsqu'un objet a des champs de référence (autres classes, listes, tableaux), MemberwiseClone() copie simplement les références. Résultat : l'original et la copie pointent vers le même objet imbriqué.

\n\n

Dans cet article, nous allons analyser le deep cloning en C# — le processus de création d'une copie totalement indépendante d'un objet avec tous ses objets imbriqués. Nous comparerons 5 approches populaires, écrirons du code réel, effectuerons un benchmark mental et déterminerons quelle méthode choisir pour votre projet.

\n\n

Qu'est-ce que le Deep Clone et en quoi diffère-t-il du Shallow Clone ?

\n\n

Imaginez que vous ayez une classe Person qui contient Address :

\n\n
public class Address\n{\n    public string City { get; set; }\n    public string Street { get; set; }\n}\n\npublic class Person\n{\n    public string Name { get; set; }\n    public int Age { get; set; }\n    public Address HomeAddress { get; set; }\n}
\n\n

Avec un shallow clone (le MemberwiseClone() standard), vous obtenez une nouvelle personne, mais son HomeAddress pointera vers le même objet Address que celui de l'original. Vous modifiez la rue dans la copie — la rue change aussi dans l'original. Catastrophe.

\n\n

Le deep clone résout ce problème : il copie récursivement tous les objets imbriqués, créant une structure de données totalement indépendante.

\n\n

Approche 1 : Implémentation manuelle via ICloneable et constructeurs de copie

\n\n

La méthode la plus évidente, mais aussi la plus laborieuse, consiste à implémenter l'interface ICloneable ou à écrire votre propre méthode DeepClone().

\n\n

Exemple d'implémentation :

\n\n
public class Address : ICloneable\n{\n    public string City { get; set; }\n    public string Street { get; set; }\n\n    public object Clone()\n    {\n        return new Address { City = this.City, Street = this.Street };\n    }\n}\n\npublic class Person : ICloneable\n{\n    public string Name { get; set; }\n    public int Age { get; set; }\n    public Address HomeAddress { get; set; }\n\n    public object Clone()\n    {\n        return new Person\n        {\n            Name = this.Name,\n            Age = this.Age,\n            HomeAddress = (Address)this.HomeAddress?.Clone()\n        };\n    }\n}
\n\n

Avantages :

\n
    \n
  • Contrôle total sur le processus de copie
  • \n
  • Performances maximales
  • \n
  • Code compréhensible (pas de "magie")
  • \n
\n\n

Inconvénients :

\n
    \n
  • Quantité énorme de code boilerplate
  • \n
  • Facile d'oublier de mettre à jour la méthode Clone() lors de l'ajout d'un nouveau champ
  • \n
  • Ne fonctionne pas pour les classes tierces (par exemple, issues de bibliothèques)
  • \n
\n\n

Quand l'utiliser : Uniquement pour des DTO simples avec 2-3 champs, où les performances sont critiques (par exemple, dans les moteurs de jeux).

\n\n

Approche 2 : Réflexion (Reflection) — le soldat universel

\n\n

La réflexion permet d'écrire une méthode universelle de deep cloning qui fonctionnera avec n'importe quel objet.

\n\n

Exemple de code :

\n\n
public static T DeepCloneByReflection<T>(T source)\n{\n    if (source == null) return default;\n\n    var type = source.GetType();\n    \n    // Pour les types valeur et les chaînes, on retourne simplement la valeur\n    if (type.IsValueType || type == typeof(string))\n        return source;\n\n    // Pour les tableaux\n    if (type.IsArray)\n    {\n        var arr = source as Array;\n        var clonedArr = Array.CreateInstance(type.GetElementType(), arr.Length);\n        for (int i = 0; i < arr.Length; i++)\n            clonedArr.SetValue(DeepCloneByReflection(arr.GetValue(i)), i);\n                

Blogs

Book Recommendations