142 votes

Comment initialiser une List<T> à une taille donnée (par opposition à la capacité) ?

.NET propose un conteneur de liste générique dont les performances sont presque identiques (voir la question sur les performances des tableaux et des listes). Cependant, ils sont très différents au niveau de l'initialisation.

Les tableaux sont très faciles à initialiser avec une valeur par défaut, et par définition ils ont déjà une certaine taille :

string[] Ar = new string[10];

ce qui permet d'attribuer en toute sécurité des éléments aléatoires, par exemple :

Ar[5]="hello";

avec une liste, les choses sont plus délicates. Je peux voir deux façons de faire la même initialisation, aucune n'est ce qu'on pourrait appeler élégante :

List<string> L = new List<string>(10);
for (int i=0;i<10;i++) L.Add(null);

ou

string[] Ar = new string[10];
List<string> L = new List<string>(Ar);

Quel serait le moyen le plus propre ?

EDIT : Les réponses données jusqu'à présent font référence à la capacité, ce qui est autre chose que de pré-remplir une liste. Par exemple, sur une liste qui vient d'être créée avec une capacité de 10, on ne peut pas faire L[2]="une valeur".

EDIT 2 : Les gens se demandent pourquoi je veux utiliser les listes de cette façon, car ce n'est pas la façon dont elles sont censées être utilisées. Je vois deux raisons :

  1. On pourrait argumenter de manière tout à fait convaincante que les listes sont les tableaux de "nouvelle génération", ajoutant de la flexibilité sans presque aucune pénalité. On devrait donc les utiliser par défaut. Je signale qu'elles ne sont peut-être pas aussi faciles à initialiser.

  2. Ce que j'écris actuellement est une classe de base offrant une fonctionnalité par défaut dans le cadre d'une structure plus large. Dans la fonctionnalité par défaut que je propose, la taille de la liste est connue à l'avance et j'aurais donc pu utiliser un tableau. Cependant, je veux offrir à n'importe quelle classe de base la possibilité de l'étendre dynamiquement et j'opte donc pour une liste.

1 votes

"EDIT : Les réponses données jusqu'à présent font référence à la capacité, ce qui est autre chose que de pré-remplir une liste. Par exemple, sur une liste qui vient d'être créée avec une capacité de 10, on ne peut pas faire L[2]="somevalue"". Compte tenu de cette modification, vous devriez peut-être reformuler le titre de la question...

0 votes

Mais, quelle est l'utilité de pré-remplir une liste avec des valeurs vides, car c'est ce que le topicstarter essaie de faire ?

0 votes

Frederik : Exactement. Quand cela serait-il nécessaire... jamais ?

0voto

Si je comprends bien, vous voulez la version List<T> de new T[size], sans la surcharge de l'ajout de valeurs à celle-ci.

Si vous ne craignez pas que l'implémentation de List<T> change radicalement à l'avenir (et dans ce cas, je pense que la probabilité est proche de 0), vous pouvez utiliser la réflexion :

    public static List<T> NewOfSize<T>(int size) {
        var list = new List<T>(size);
        var sizeField = list.GetType().GetField("_size",BindingFlags.Instance|BindingFlags.NonPublic);
        sizeField.SetValue(list, size);
        return list;
    }

Notez que cela prend en compte la fonctionnalité par défaut du tableau sous-jacent qui consiste à pré-remplir avec la valeur par défaut du type d'élément. Tous les tableaux de type int auront la valeur 0 et tous les tableaux de type référence auront la valeur null. Notez également que pour une liste de types de référence, seul l'espace pour le pointeur de chaque élément est créé.

Si, pour une raison ou une autre, vous décidez de ne pas utiliser la réflexion, j'aurais aimé offrir une option de AddRange avec une méthode de générateur, mais en dessous, List<T> appelle simplement Insert un zillion de fois, ce qui ne sert à rien.

Je tiens également à souligner que la classe Array possède une méthode statique appelée ResizeArray, si vous souhaitez faire le chemin inverse et partir de Array.

Pour finir, je déteste vraiment quand je pose une question et que tout le monde me fait remarquer que c'est la mauvaise question. Peut-être que ça l'est, et merci pour l'info, mais j'aimerais quand même avoir une réponse, parce que vous n'avez aucune idée de pourquoi je la pose. Ceci étant dit, si vous voulez créer un cadre qui utilise les ressources de manière optimale, List<T> est une classe plutôt inefficace pour autre chose que pour contenir et ajouter des choses à la fin d'une collection.

0voto

Charlieface Points 8681

C'est une vieille question, mais j'ai deux solutions. L'une est une réflexion rapide et sale ; l'autre est une solution qui répond en fait à la question (définir le taille pas le capacité ) tout en étant performant, ce qu'aucune des réponses ici ne fait.


Réflexion

C'est rapide et sale, et ce que fait le code devrait être assez évident. Si vous voulez l'accélérer, mettez en cache le résultat de GetField, ou créez une DynamicMethod pour le faire :

public static void SetSize<T>(this List<T> l, int newSize) =>
    l.GetType().GetField("_size", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(l, 10);

Il est évident que beaucoup de gens hésiteront à mettre un tel code en production.


ICollection<T>

Cette solution est basée sur le fait que le constructeur List(IEnumerable<T> collection) optimise pour ICollection<T> et ajuste immédiatement la taille à la quantité correcte, sans itération. Il appelle ensuite les collections CopyTo pour faire la copie.

Le code est le suivant :

public List(IEnumerable<T> collection) {
....
    ICollection<T> c = collection as ICollection<T>;
    if (collection is ICollection<T> c)
    {
        int count = c.Count;
        if (count == 0)
        {
            _items = s_emptyArray;
        }
        else {
            _items = new T[count];
            c.CopyTo(_items, 0);
            _size = count;
        }
    }    

Nous pouvons donc pré-initialiser la liste à la bonne taille de manière totalement optimale, sans aucune copie supplémentaire.

Comment ? En créant un ICollection<T> qui ne fait rien d'autre que de retourner un Count . Plus précisément, nous allons pas mettre en œuvre quoi que ce soit dans CopyTo qui est la seule autre fonction appelée.

private class SizeCollection<T> : ICollection<T>
{
    public SizeCollection(int size) =>
        Count = size;

    public void Add(T i){}
    public void Clear(){}
    public bool Contains(T i)=>true;
    public void CopyTo(T[]a, int i){}
    public bool Remove(T i)=>true;
    public int Count {get;}
    public bool IsReadOnly=>true;
    public IEnumerator<T> GetEnumerator()=>null;
    IEnumerator IEnumerable.GetEnumerator()=>null;
}

public List<T> InitializedList<T>(int size) =>
    new List<T>(new SizeCollection<T>(size));

Nous pourrions en théorie faire la même chose pour AddRange / InsertRange pour un tableau existant, qui tient également compte ICollection<T> mais le code crée un nouveau tableau pour les éléments supposés, puis les copie. Dans ce cas, il serait plus rapide de simplement faire une boucle vide. Add :

public void SetSize<T>(this List<T> l, int size)
{
    if(size < l.Count)
        l.RemoveRange(size, l.Count - size);
    else
        for(size -= l.Count; size > 0; size--)
            l.Add(default(T));
}

-1voto

Victor Drouin Points 480

Un peu tard mais la première solution que vous avez proposée me semble beaucoup plus propre : vous n'allouez pas la mémoire deux fois. Même le constructeur de liste doit parcourir le tableau en boucle pour le copier ; il ne sait même pas d'avance s'il n'y a que des éléments nuls à l'intérieur.

1. - allouer N - boucle N Coût : 1 * allocate(N) + N * loop_iteration

2. - allouer N - allouer N + boucle () Coût : 2 * allocate(N) + N * loop_iteration

Cependant, l'allocation de la liste et des boucles pourrait être plus rapide puisque la liste est une classe intégrée, mais le C# est compilé en jit, donc...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X