Veuillez activer Javascript pour voir le contenu

[.NET] Comment réinitialiser une collection proprement: clear(), new() ou null ?

· ☕ 5 min de lecture

On m’a récemment posé la question sur “quelle est la “meilleure” méthode pour supprimer les éléments d’une collection à taille variable”.

  • myCollection.Clear()
  • myCollection = new List()
  • myCollection = null

Selon moi comme toujours il n’y a pas de “meilleure” solution ou même de “méthode magique”, tout dépend de ce qu’on veut réellement faire et ce qu’on veut transmettre comme message aux prochains développeurs qui liront le code.

Nous ne traiterons pas le cas des collections thread safe de type ConcurrentBag & co. où la notion de concurrence rentre en jeu (en excluant ce point la logique reste identique).

Avant toutes choses du point de vue de la performance et en prenant en critère la volonté de supprimer les éléments du tableau sans aller dans la micro-optimisation, les 3 méthodes peuvent être considérée comme équivalente = les éléments seront supprimés par le GC.

Alors peu importe la solution ça ne change rien ?

Non, car même si la finalité sur les objets de la collection est la même, au globale le résultat est différent.

Voici les classes utilisées dans les exemples, ces dernières ne sont utilisées que pour obtenir facilement un status visible de leur destruction.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class Foo
{
    private readonly int _id;

    public Foo(int id)
    {
        _id = id;
    }

    ~Foo()
    {
        Console.WriteLine($"Free Foo_{_id}");
    }
}
1
2
3
4
5
6
7
public class TestList<T> : List<T>
{
    ~TestList()
    {
        Console.WriteLine("Free TestList");
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
private static TestList<Foo> GetTestData()
{
    var foos = new TestList<Foo>();
    for (var i = 0; i < 2; i++)
    {
        foos.Add(new Foo(i));
    }

    return foos;
}

null

Assigner la collection à null revient à pousser un référence null et à l’assigner à notre variable.

1
2
3
4
5
6
7
8
9
var foos = GetTestData();
foos = null;

GC.Collect();
GC.KeepAlive(foos);

// Free Foo_1
// Free Foo_0
// Free TestList

Cela signifie que notre collection initiale n’est plus rattachée aux GC Roots, elle sera donc considérée comme morte au prochain passage du GC.
Et comme cette dernière possède des objets rattachés qui n’ont pas d’autres références ils seront supprimés par la même occasion.
Donc cette méthode supprime la collection et les objets rattachés.

Il est strictement inutile d’affecter un objet à null en fin de scope, en effet une fois le scope passé l’objet n’est de fait plus rattaché aux GC Roots (s’il n’est pas référencé ailleurs).
1
2
3
4
{
  var foos = GetTestData();
  foos = null;
}

Est identique à

1
2
3
{
  var foos = GetTestData();
}

new()

Similaire à la méthode précédente à la différence que c’est une nouvelle instance de classe (=nouvelle référence) qui est poussée dans la pile d’évaluation et non null.

1
2
3
4
5
6
7
8
9
var foos = GetTestData();
foos = new TestList<Foo>();

GC.Collect();
GC.KeepAlive(foos);

// Free Foo_1
// Free Foo_0
// Free TestList

Comme avec null, la collection est supprimée dû au fait que sa référence n’est plus liée aux GC Roots et par effet de chaîne les objets rattachés aussi.

Clear()

D’après la documentation cette méthode “supprime tous les éléments”.
En vérité elle remplace les références avec la collection, c’est le GC qui supprimera les objets de la mémoire.
Le plus simple est de voir ce qui se passe en vrai.

1
2
3
4
5
6
7
8
 var foos = GetTestData();
foos.Clear();

GC.Collect();
GC.KeepAlive(foos);

// Free Foo_1
// Free Foo_0

Contrairement aux méthodes précédentes ici on ne touche pas à la référence de la collection mais seulement aux références des objets de celle-ci.
Résultat la collection étant toujours lié aux GC Roots, elle n’est pas libérée mais comme les objets Foo_1 et Foo_2 ne sont plus liés à la collection ni rien d’autre ils sont considérés comme mort par le GC et ainsi se dernier les libère.
Aussi la collection reste la même de ce fait ces propriétés acquises dont sa référence ou la capacité reste inchangée.

Pour rappel une List est simplement un tableau qui se redimensionne à la hausse automatiquement par réaffectation.
Par défaut la taille de ce tableau est de 4, si par l’accumulation de données le tableau est de taille 1000, il restera à 1000 après le passage de la méthode Clear(), cela évite de réallouer des espaces lors des 1000 prochains ajouts, mais cela implique que vous avez un tableau de taille 1000 en mémoire.

Résumé

méthode quand ?
null si vous souhaitez libérer la mémoire allouée par la collection et son contenu
new() si vous souhaitez libérer la mémoire allouée par la collection et son contenu tout en créant une nouvelle instance de la collection et implicitement une nouvelle capacité de base
clear() si vous souhaitez libérer uniquement le contenu de la collection, en gardant les propriétés acquises de la collection

Conclusion

Il est important de bien utiliser chacunes des 3 méthodes avec pertinence car elles aident à la compréhension du code.
Micro-optimisation mis à part, le code doit avant tout être compréhensible immédiatement.

  • si je vois Clear() je comprend de suite la volonté de supprimer le contenu
  • si je vois new List<T>(400) je comprend directement la volonté de repartir sur une collection vierge de taille 400 car dans le contexte 400 est la bonne taille et que précédemment la collection était bien trop grosse
  • si je vois null c’est que la collection ne sera plus utilisées plus tard dans le code et potentiellement que sa taille est problématique

Si on s’attarde sur la micro-optimisation (on parle de seulement quelques Ticks…) un new List<T>(x) est plus performant qu’un Clear() car dans ce dernier un parcours du tableau est requis (avec une compléxité O(n)), en revanche le message fourni aux prochains développeurs peut être ambigu à la première lecture (en bref cela ne vaut pas le coup dans la quasi totalité des cas).

Sources

Documentation

Partager sur

Jérémy Landon
ÉCRIT PAR
Jérémy Landon
Freelance / Author / Speaker / Open source contributor

Contenu de cette page