Array versus List <T>: Quand utiliser quoi?

594
MyClass[] array;
List<MyClass> list;

Quels sont les scénarios où l'un est préférable à l'autre? Et pourquoi?

Frédéric le fou
la source
9
Les tableaux sont plutôt obsolètes, comme on le voit dans une discussion populaire ici. Également souligné ici , et par notre hôte dans le blog .
gimel
9
Si je ne me trompe pas, la liste <> a un tableau comme structure interne. Chaque fois que le tableau interne est rempli, il suffit de copier le contenu dans un tableau qui est le double de la taille (ou d'autres fois constantes la taille actuelle). en.wikipedia.org/wiki/Dynamic_array
Ykok
Ykok: Ce que vous dites semble juste, j'ai trouvé le code source de List <> ici .
Carol
19
@gimel Arguant que les tableaux sont obsolètes est peut-être un peu audacieux
awdz9nld

Réponses:

580

Il est rare, en réalité, que vous souhaitiez utiliser un tableau. Utilisez List<T>certainement n'importe quand vous souhaitez ajouter / supprimer des données, car le redimensionnement des tableaux est coûteux. Si vous savez que les données sont de longueur fixe et que vous souhaitez micro-optimiser pour une raison très spécifique (après l'analyse comparative), un tableau peut être utile.

List<T>offre beaucoup plus de fonctionnalités qu'un tableau (bien que LINQ égalise un peu), et est presque toujours le bon choix. Sauf pour les paramsarguments, bien sûr. ;-p

En tant que compteur - List<T>est unidimensionnel; où-comme vous avez des tableaux rectangulaires (etc) comme int[,]ou string[,,]- mais il existe d'autres façons de modéliser ces données (si vous en avez besoin) dans un modèle d'objet.

Voir également:

Cela dit, j'utilise beaucoup les tableaux dans mon projet protobuf-net ; entièrement pour la performance:

  • il fait beaucoup de décalage de bits, donc a byte[]est à peu près essentiel pour l'encodage;
  • J'utilise un byte[]tampon de roulement local que je remplis avant de l'envoyer vers le flux sous-jacent (et vv); plus rapide que BufferedStreametc;
  • il utilise en interne un modèle d'objets basé sur un tableau ( Foo[]plutôt que List<Foo>), car la taille est fixe une fois construite et doit être très rapide.

Mais c'est définitivement une exception; pour le traitement général du secteur d'activité, un List<T>gagne à chaque fois.

Marc Gravell
la source
8
L'argument du redimensionnement est totalement valable. Cependant, les gens préfèrent les listes même si aucun redimensionnement n'est nécessaire. Pour ce dernier cas, existe-t-il un argument solide et logique ou n'est-ce rien de plus que "les tableaux sont démodés"?
Frederick The Fool le
6
"Utilisez certainement une liste <T> chaque fois que vous souhaitez ajouter / supprimer des données, car le redimensionnement des tableaux coûte cher." La liste <T> utilise un tableau en interne. Pensiez-vous à LinkedList <T>?
dan-gph
14
Plus de fonctionnalités == plus complexe == pas bon, sauf si vous avez besoin de ces fonctionnalités. Cette réponse énumère essentiellement les raisons pour lesquelles les tableaux sont meilleurs, mais tire la conclusion opposée.
Eamon Nerbonne
12
@EamonNerbonne si vous n'utilisez pas ces fonctionnalités, je peux à peu près garantir qu'elles ne vont pas vous blesser ... mais: le nombre de collections qui n'ont jamais besoin de mutation est beaucoup plus petit, selon mon expérience, que celles qui le sont muté
Marc Gravell
7
@MarcGravell: cela dépend de votre style de codage. D'après mon expérience, pratiquement aucune collection n'est mutée. C'est; les collections sont extraites de la base de données ou construites à partir d'une source, mais le traitement est toujours effectué en recréant une nouvelle collection (par exemple, carte / filtre, etc.). Même lorsqu'une mutation conceptuelle est nécessaire, il est généralement plus simple de générer simplement une nouvelle collection. Je ne mute qu'une collection en tant qu'optimisation des performances, et ces optimisations ont tendance à être très locales et à ne pas exposer la mutation aux consommateurs d'API.
Eamon Nerbonne
121

Vraiment juste répondre pour ajouter un lien qui me surprend n'a pas encore été mentionné: l'entrée de blog d'Eric Lippert sur "Les tableaux sont considérés comme quelque peu nocifs".

Vous pouvez juger du titre qu'il suggère d'utiliser des collections partout où cela est pratique - mais comme Marc le fait remarquer à juste titre, il existe de nombreux endroits où un tableau est vraiment la seule solution pratique.

Jon Skeet
la source
2
Enfin, j'ai fini par lire ceci plus de 3 ans plus tard haha. Bon article alors, bon article maintenant. :)
Spencer Ruport
21

Nonobstant les autres réponses recommandées List<T>, vous voudrez utiliser des tableaux lors de la manipulation:

  • données bitmap d'image
  • autres structures de données de bas niveau (c.-à-d. protocoles de réseau)
Alnitak
la source
1
Pourquoi des protocoles réseau? Ne préférez-vous pas utiliser des structures personnalisées ici et leur donner un sérialiseur spécial ou une disposition de mémoire explicite? De plus, qu'est-ce qui parle contre l'utilisation List<T>d'un tableau ici plutôt que d'un tableau d'octets?
Konrad Rudolph
8
@Konrad - enfin, pour les démarreurs, Stream.Read et Stream.Write fonctionnent avec l'octet [], tout comme Encoding etc ...
Marc Gravell
12

Sauf si vous êtes vraiment préoccupé par les performances, et par là je veux dire, "Pourquoi utilisez-vous .Net au lieu de C ++?" vous devriez vous en tenir à Liste <>. Il est plus facile à entretenir et fait tout le sale boulot de redimensionner un tableau en arrière-plan pour vous. (Si nécessaire, List <> est assez intelligent pour choisir les tailles de tableau, il n'est donc pas nécessaire de le faire habituellement.)

Spencer Ruport
la source
15
"Pourquoi utilisez-vous .Net au lieu de C ++?" XNA
Bengt
6

Les tableaux doivent être utilisés de préférence à List lorsque l'immuabilité de la collection elle-même fait partie du contrat entre le code client et fournisseur (pas nécessairement l'immuabilité des éléments de la collection) ET lorsque IEnumerable ne convient pas.

Par exemple,

var str = "This is a string";
var strChars = str.ToCharArray();  // returns array

Il est clair que la modification de "strChars" ne mutera pas l'objet "str" ​​d'origine, quelle que soit la connaissance au niveau de l'implémentation du type sous-jacent de "str".

Mais supposons que

var str = "This is a string";
var strChars = str.ToCharList();  // returns List<char>
strChars.Insert(0, 'X');

Dans ce cas, il n'est pas clair à partir de cet extrait de code seul si la méthode d'insertion mutera ou non l'objet "str" ​​d'origine. Il faut une connaissance de String de la mise en œuvre pour faire cette détermination, ce qui rompt l'approche Design by Contract. Dans le cas de String, ce n'est pas un gros problème, mais cela peut être un gros problème dans presque tous les autres cas. La définition de la liste en lecture seule aide mais entraîne des erreurs d'exécution, pas de compilation.

Herman Schoenfeld
la source
Je suis relativement nouveau dans C #, mais je ne comprends pas pourquoi le retour d'une liste suggérerait la mutabilité des données d'origine d'une manière qui ne le serait pas. J'aurais cependant pensé qu'une méthode dont le nom commence par Tova créer un objet qui n'a pas la capacité de modifier l'instance d'origine, contrairement à strChars as char[]ce qui, s'il est valide, suggérerait que vous êtes maintenant en mesure de modifier l'objet d'origine.
Tim MB
@TimMB Il y a l'immuabilité de la collection (ne peut pas ajouter d'éléments distants) et l'immuabilité des éléments de la collection. Je parlais de ce dernier, alors que vous confondez probablement les deux. Le retour d'un tableau garantit au client qu'il ne peut pas ajouter / supprimer d'éléments. Si c'est le cas, il réalloue la baie et est assuré que cela n'affectera pas l'original. En renvoyant une liste, aucune assurance de ce type n'est faite et l'original pourrait être affecté (dépend de la mise en œuvre). La modification des éléments dans la collection (qu'il s'agisse d'un tableau ou d'une liste) peut affecter l'original, si le type de l'élément n'est pas une structure.
Herman Schoenfeld
Merci pour la clarification. Je suis toujours confus (probablement parce que je viens du monde C ++). Si en strinterne utilise un tableau et ToCharArrayrenvoie une référence à ce tableau, le client peut muter stren modifiant les éléments de ce tableau, même si la taille reste fixe. Pourtant, vous écrivez «Il est clair que la modification de« strChars »ne mutera pas l'objet« str »d'origine». Qu'est-ce que j'oublie ici? D'après ce que je peux voir, dans les deux cas, le client peut avoir accès à la représentation interne et, quel que soit le type, cela permettrait une mutation quelconque.
Tim MB
3

Si je sais exactement combien d'éléments que je vais avoir besoin, dire que je besoin de 5 éléments et que jamais 5 éléments puis - je utiliser un tableau. Sinon, j'utilise simplement une liste <T>.

smack0007
la source
1
Pourquoi n'utilisez-vous pas une liste <T> dans le cas où vous connaissez le nombre d'éléments?
Oliver
3

La plupart du temps, utiliser un Listsuffirait. A Listutilise un tableau interne pour gérer ses données et redimensionne automatiquement le tableau lors de l'ajout de plus d'éléments à Listsa capacité actuelle, ce qui le rend plus facile à utiliser qu'un tableau, où vous devez connaître la capacité au préalable.

Voir http://msdn.microsoft.com/en-us/library/ms379570(v=vs.80).aspx#datastructures20_1_topic5 pour plus d'informations sur les listes en C # ou simplement décompiler System.Collections.Generic.List<T>.

Si vous avez besoin de données multidimensionnelles (par exemple en utilisant une matrice ou en programmation graphique), vous iriez probablement avec un à la arrayplace.

Comme toujours, si la mémoire ou les performances sont un problème, mesurez-le! Sinon, vous pourriez faire de fausses hypothèses sur le code.

Sune Rievers
la source
1
Salut, pourriez-vous expliquer pourquoi "le temps de recherche d'une liste serait O (n)" est vrai? Autant que je sache, List <T> utilise un tableau en arrière-plan.
libellule
1
@dragonfly vous avez tout à fait raison. Source . À l'époque, je supposais que l'implémentation utilisait des pointeurs, mais j'ai depuis appris le contraire. À partir du lien ci-dessus: «La récupération de la valeur de cette propriété est une opération O (1); la définition de la propriété est également une opération O (1). »
Sune Rievers
2

Arrays Vs. Les listes sont un problème classique de maintenabilité par rapport aux performances. La règle générale que presque tous les développeurs suivent est que vous devez viser les deux, mais lorsqu'ils entrent en conflit, choisissez la maintenabilité plutôt que les performances. L'exception à cette règle est lorsque les performances se sont déjà révélées être un problème. Si vous portez ce principe dans Arrays Vs. Listes, alors ce que vous obtenez est le suivant:

Utilisez des listes fortement typées jusqu'à ce que vous rencontriez des problèmes de performances. Si vous rencontrez un problème de performances, décidez si le décrochage vers les baies bénéficiera davantage à votre solution en termes de performances qu’au détriment de votre solution en termes de maintenance.

Développeur Melbourne
la source
1

Une autre situation non encore mentionnée est celle où l'on aura un grand nombre d'éléments, dont chacun se compose d'un tas fixe de variables liées mais indépendantes collées ensemble (par exemple les coordonnées d'un point ou les sommets d'un triangle 3D). Un tableau de structures de champ exposé permettra à ses éléments d'être efficacement modifiés "en place" - ce qui n'est pas possible avec tout autre type de collection. Parce qu'un tableau de structures contient ses éléments consécutivement dans la RAM, les accès séquentiels aux éléments du tableau peuvent être très rapides. Dans les situations où le code devra effectuer de nombreux passages séquentiels à travers un tableau, un tableau de structures peut surpasser un tableau ou une autre collection de références d'objet de classe d'un facteur 2: 1; plus loin,

Bien que les tableaux ne soient pas redimensionnables, il n'est pas difficile de faire en sorte que le code stocke une référence de tableau avec le nombre d'éléments utilisés et remplace le tableau par un plus grand si nécessaire. Alternativement, on pourrait facilement écrire du code pour un type qui se comportait un peu comme un List<T>mais exposait sa mémoire de sauvegarde, permettant ainsi de dire soit MyPoints.Add(nextPoint);ou MyPoints.Items[23].X += 5;. Notez que ce dernier ne lèverait pas nécessairement une exception si le code tentait d'accéder au-delà de la fin de la liste, mais l'utilisation serait sinon conceptuellement assez similaire à List<T>.

supercat
la source
Ce que vous avez décrit est une liste <>. Il y a un indexeur pour que vous puissiez accéder directement au tableau sous-jacent, et la liste <> conservera la taille pour vous.
Carl
@Carl: Étant donné par exemple Point[] arr;, il est possible pour le code de dire, par exemple arr[3].x+=q;. En utilisant par exemple List<Point> list, il faudrait plutôt dire Point temp=list[3]; temp.x+=q; list[3]=temp;. Il serait utile d' List<T>avoir une méthode Update<TP>(int index, ActionByRefRef<T,TP> proc, ref TP params). et les compilateurs pourraient se transformer list[3].x+=q;en{list.Update(3, (ref int value, ref int param)=>value+=param, ref q); mais aucune telle fonctionnalité n'existe.
supercat
Bonnes nouvelles. Ça marche.list[0].X += 3;ajoutera 3 à la propriété X du premier élément de la liste. Et listest une List<Point>et Pointest une classe avec des propriétés X et Y
Carl
1

Les listes dans .NET sont des wrappers sur des tableaux et utilisent un tableau en interne. La complexité temporelle des opérations sur les listes est la même que pour les tableaux, mais il y a un peu plus de surcharge avec toutes les fonctionnalités / facilité d'utilisation des listes (telles que le redimensionnement automatique et les méthodes fournies avec la classe de liste). À peu près, je recommanderais d'utiliser des listes dans tous les cas, sauf s'il existe une raison impérieuse de ne pas le faire, comme si vous avez besoin d'écrire du code extrêmement optimisé ou si vous travaillez avec un autre code construit autour de tableaux.

iliketocode
la source
0

Plutôt que de passer par une comparaison des fonctionnalités de chaque type de données, je pense que la réponse la plus pragmatique est "les différences ne sont probablement pas si importantes pour ce que vous devez accomplir, d'autant plus qu'elles implémentent toutes les deux IEnumerable , alors suivez la convention populaire et utilisez un Listjusqu'à ce que vous ayez une raison de ne pas le faire, auquel cas vous aurez probablement votre raison d'utiliser un tableau sur un List".

La plupart du temps, dans le code managé, vous souhaiterez privilégier les collections avec autant de facilité de travail que de vous soucier des micro-optimisations.

moarboilerplate
la source
0

Ils peuvent être impopulaires, mais je suis un fan d'Arrays dans les projets de jeux. - La vitesse d'itération peut être importante dans certains cas, car chaque sur un tableau a beaucoup moins de frais généraux si vous ne faites pas beaucoup par élément - L'ajout et la suppression ne sont pas si difficiles avec les fonctions d'assistance - C'est plus lent, mais dans les cas où vous ne le construisez qu'une seule fois cela peut ne pas avoir d'importance - Dans la plupart des cas, moins de mémoire supplémentaire est gaspillée (seulement vraiment significative avec les tableaux de structures) - Légèrement moins de déchets et de pointeurs et de chasse aux pointeurs

Cela étant dit, j'utilise List beaucoup plus souvent que Arrays dans la pratique, mais ils ont chacun leur place.

Ce serait bien si List où un type intégré afin qu'ils puissent optimiser l'encapsulage et les frais généraux d'énumération.


la source
0

Remplir une liste est plus facile qu'un tableau. Pour les tableaux, vous devez connaître la longueur exacte des données, mais pour les listes, la taille des données peut être quelconque. Et, vous pouvez convertir une liste en tableau.

List<URLDTO> urls = new List<URLDTO>();

urls.Add(new URLDTO() {
    key = "wiki",
    url = "https://...",
});

urls.Add(new URLDTO()
{
    key = "url",
    url = "http://...",
});

urls.Add(new URLDTO()
{
    key = "dir",
    url = "https://...",
});

// convert a list into an array: URLDTO[]
return urls.ToArray();
Bimal Poudel
la source
0

Puisque personne ne mentionne: En C #, un tableau est une liste. MyClass[]et les List<MyClass>deux implémentent IList<MyClass>. (par exemple void Foo(IList<int> foo)peut être appelé comme Foo(new[] { 1, 2, 3 })ouFoo(new List<int> { 1, 2, 3 }) )

Donc, si vous écrivez une méthode qui accepte un List<MyClass>comme argument, mais n'utilise qu'un sous-ensemble de fonctionnalités, vous pouvez déclarer commeIList<MyClass> place pour la commodité des appelants.

Détails:

snipsnipsnip
la source
"En C #, un tableau est une liste" Ce n'est pas vrai; un tableau n'est pas un List, il implémente uniquement l' IListinterface.
Rufus L
-1

Cela dépend complètement des contextes dans lesquels la structure de données est nécessaire. Par exemple, si vous créez des éléments à utiliser par d'autres fonctions ou services, List est le moyen idéal pour y parvenir.

Maintenant, si vous avez une liste d'éléments et que vous souhaitez simplement les afficher, disons que sur un tableau de pages Web est le conteneur que vous devez utiliser.

sajidnizami
la source
1
Si vous avez une liste d'articles et que vous souhaitez simplement les afficher, alors qu'est-ce qui ne va pas en utilisant simplement la liste que vous avez déjà? Que proposerait un tableau ici?
Marc Gravell
1
Et pour "créer des éléments à utiliser par d'autres fonctions ou services", en fait, je préférerais un bloc d'itérateur avec IEnumerable<T>- alors je peux diffuser des objets plutôt que de les mettre en mémoire tampon.
Marc Gravell
-1

Il convient de mentionner la possibilité de lancer sur place.

interface IWork { }
class Foo : IWork { }

void Test( )
{
    List<Foo> bb = new List<Foo>( );
    // Error: CS0029 Cannot implicitly convert type 'System.Collections.Generic.List<Foo>' to 'System.Collections.Generic.List<IWork>'
    List<IWork> cc = bb; 

    Foo[] bbb = new Foo[4];
    // Fine
    IWork[] ccc = bbb;
}

Ainsi, Array offre un peu plus de flexibilité lorsqu'il est utilisé dans le type de retour ou l'argument pour les fonctions.

IWork[] GetAllWorks( )
{
    List<Foo> fooWorks = new List<Foo>( );
    return fooWorks.ToArray( ); // Fine
}

void ExecuteWorks( IWork[] works ) { } // Also accept Foo[]
Wappenull
la source
Vous devez noter que la variance du tableau est cassée .
mcarton