Il semble que List<T>
en C # peut faire tout ce qu'un tableau peut faire et bien plus, et semble aussi efficace en mémoire et en performances qu'un tableau.
Alors pourquoi voudrais-je jamais utiliser un tableau?
Évidemment, je ne pose pas de questions sur les cas où une API ou une autre contrainte externe (c'est-à-dire la fonction Main) m'oblige à utiliser un tableau ... Je ne pose que des questions sur la création de nouvelles structures de données dans mon propre code.
La même raison pour laquelle je ne conduis pas de camion pour aller travailler. Je n'utilise pas quelque chose dont je n'utiliserai pas les fonctionnalités.
Tout d'abord, un tableau est une construction primitive, donc un tableau est plus rapide et plus efficace qu'un List <> bien sûr, donc votre argument n'est pas vrai. Array est également disponible partout et connu des développeurs utilisant différents langages et plates-formes.
La raison la plus importante pour laquelle j'utilise un tableau au lieu d'une liste <> est d'impliquer que les données sont longueur fixe. Si je n'ajoute ou ne supprime aucun élément de cette collecte de données, je veux m'assurer que le type reflète cela.
Autre chose, disons que vous implémentez une nouvelle structure de données et que vous avez lu des articles à ce sujet. Désormais, lors de l'implémentation d'algorithmes spécifiques, vous ne pouvez pas toujours vous fier à l'implémentation par quelqu'un d'autre d'un type à usage général. Il passe de .NET à Mono et même entre différentes versions du framework.
Et il est parfois plus facile de porter un morceau de code qui utilise un tableau au lieu d'un type dépendant du framework.
Vous avez besoin de tableaux pour gérer votre collection de structures mutables, bien sûr, et que ferions-nous sans ceux-ci.
struct EvilMutableStruct { public double X; } // don't do this
EvilMutableStruct[] myArray = new EvilMutableStruct[1];
myArray[0] = new EvilMutableStruct()
myArray[0].X = 1; // works, this modifies the original struct
List<EvilMutableStruct> myList = new List<EvilMutableStruct>();
myList.Add(new EvilMutableStruct());
myList[0].X = 1; // does not work, the List will return a *copy* of the struct
(notez qu'il peut y avoir des cas où un tableau de structures mutables est souhaitable, mais généralement ce comportement différent des structures mutables au sein des tableaux par rapport aux autres collections est une source d'erreurs qui doivent être évitées)
Plus sérieusement, vous avez besoin d'un tableau si vous voulez passer un élément par référence. c'est à dire.
Interlocked.Increment(ref myArray[i]); // works
Interlocked.Increment(ref myList[i]); // does not work, you can't pass a property by reference
Cela peut être utile pour le code threadsafe sans verrouillage.
Vous avez besoin d'un tableau si vous voulez rapidement et efficacement initialiser votre collection de taille fixe avec la valeur par défaut.
double[] myArray = new double[1000]; // contains 1000 '0' values
// without further initialisation
List<double> myList = new List<double>(1000) // internally contains 1000 '0' values,
// since List uses an array as backing storage,
// but you cannot access those
for (int i =0; i<1000; i++) myList.Add(0); // slow and inelegant
(notez qu'il serait possible d'implémenter un constructeur pour List qui fait de même, c'est juste que c # n'offre pas cette fonctionnalité)
vous avez besoin d'un tableau si vous voulez copier efficacement parties de la collection
Array.Copy(array1, index1, array2, index2, length) // can't get any faster than this
double[,] array2d = new double[10,100];
double[] arraySerialized = new double[10*100];
Array.Copy(array2d, 0, arraySerialized, 0, arraySerialized.Length);
// even works for different dimensions
(encore une fois, c'est quelque chose qui pourrait également être implémenté pour List, mais cette fonctionnalité n'existe pas en c #)
Alors pourquoi voudrais-je jamais utiliser un tableau?
Rarement, vous aurez un scénario où vous savez que vous avez besoin d'un nombre fixe d'éléments. Du point de vue de la conception, cela devrait être évité. Si vous avez besoin de 3 choses, la nature de l'entreprise signifie que vous en aurez très souvent besoin dans la prochaine version.
Pourtant, lorsque ce scénario rare se produit, l'utilisation d'un tableau pour appliquer cet invariant de taille fixe est utile. Il signale aux autres programmeurs qu'il s'agit d'une taille fixe et empêche toute utilisation abusive lorsque quelqu'un ajoute ou supprime un élément, ce qui brise les attentes ailleurs dans le code.
Votre question a effectivement déjà répondu auparavant .
et semble également aussi efficace en mémoire et en performances qu'un tableau.
Ça ne l'est pas. De la question que j'ai liée:
List/foreach: 3054ms (589725196)
Array/foreach: 1860ms (589725196)
Les tableaux sont deux fois plus rapides dans certains cas importants. Je suis certain que l'utilisation de la mémoire diffère également de manière non triviale.
Puisque la prémisse principale de votre question est ainsi défaite, je suppose que cela répond à votre question. En plus de cela, des tableaux vous sont parfois imposés par l'API Win32, le shader de votre GPU ou une autre bibliothèque non DotNet.
Même dans DotNet, certaines méthodes consomment et/ou renvoient des tableaux (tels que String.Split
). Ce qui signifie que vous devez maintenant manger le coût d'appeler ToList
et ToArray
tout le temps, ou vous devez vous conformer et utiliser un tableau, poursuivant éventuellement le cycle en le propageant aux pauvres utilisateurs en aval de votre code .
Plus de questions et réponses sur Stack Overflow sur ce sujet:
Cela vaut en fait pour d'autres langages qui ont également des listes (comme Java ou Visual Basic). Dans certains cas, vous devez utiliser un tableau car une méthode renvoie un tableau au lieu d'une liste.
Dans un programme réel, je ne pense pas qu'un tableau sera utilisé très souvent, mais parfois vous savez que les données seront de taille fixe et vous aimez le petit gain de performances que vous obtenez en utilisant un tableau. La micro-optimisation serait une raison valable, tout comme une méthode renvoyant une liste, ou la nécessité d'une infrastructure de données multidimensionnelle.
En plus des raisons énumérées dans d'autres réponses, le littéral de tableau prend moins de caractères à déclarer:
var array = new [] { "A", "B", "C" };
var list = new List<string>() { "A", "B", "C" };
L'utilisation de tableau au lieu de List
rend le code un peu plus court et juste un peu plus lisible dans les cas où (1) vous devez passer tout IEnumerable<T>
littéral, ou (2) où les autres fonctionnalités de List
n'ont pas d'importance et vous devez utiliser un littéral de type liste.
Je l'ai fait de temps en temps lors de tests unitaires.
C'est strictement du point de vue OO.
Bien que je ne puisse pas penser à une raison de passer juste un tableau, je peux certainement voir des situations où une représentation de tableau interne à la classe est probablement le meilleur choix.
Bien qu'il existe d'autres options qui donnent des caractéristiques similaires, aucune ne semble aussi intuitive qu'un tableau pour les problèmes de traitement des permutations, imbriqué pour les boucles, la représentation matricielle, les bitmaps et les algorithmes d'entrelacement des données.
Il existe un nombre important de domaines scientifiques qui reposent largement sur les mathématiques matricielles. (par exemple, traitement d'image, correction d'erreurs de données, traitement de signal numérique, une rame de problèmes mathématiques appliqués). La plupart des algorithmes dans ces domaines sont écrits en termes d'utilisation de matrices/matrices multidimensionnelles. Il serait donc plus naturel de mettre en œuvre les algorithmes tels qu'ils sont définis plutôt que de les rendre plus "logiciels" conviviaux au détriment de la perte des liens directs avec les documents sur lesquels les algorithmes sont basés.
Comme je l'ai dit, dans ces cas, vous pouvez probablement vous en tirer avec des listes, mais cela ajoute une couche de complexité supplémentaire à des algorithmes déjà complexes.
Eh bien, j'ai trouvé une utilisation pour les tableaux dans un jeu que j'écris. Je l'ai utilisé pour créer un système d'inventaire avec un nombre fixe de slots. Cela avait plusieurs avantages:
Je me suis dit que si jamais je devais "augmenter" la taille de l'inventaire, je pouvais le faire en transférant les anciens éléments dans le nouveau tableau, mais comme l'inventaire était fixé par l'espace d'écran et je n'avais pas besoin de l'agrandir dynamiquement/plus petit, il fonctionnait bien dans le but pour lequel je l'utilisais.
Si vous parcourez tous les éléments d'une liste, alors non, un tableau n'est pas nécessaire, la "sélection" suivante ou arbitraire "sans remplacement" fera l'affaire.
Mais si votre algorithme a besoin d'un accès aléatoire aux éléments de la collection, alors, oui, un tableau est nécessaire.
Ceci est quelque peu analogue à "est-il nécessaire?". Dans une langue moderne raisonnable, ce n'est pas du tout nécessaire. Mais si vous décollez les abstractions, à un moment donné, c'est tout ce qui vous est réellement disponible, c'est-à-dire que la seule façon de mettre en œuvre ces abstractions est avec la fonctionnalité `` inutile ''. (Bien sûr, l'analogie n'est pas parfaite, je pense que personne ne dit que les tableaux sont de mauvaises pratiques de programmation; ils sont faciles à comprendre et à penser).
Compatibilité héritée.
Tous forment une expérience personnelle:
Programmeurs hérités - mon collègue utilise des tableaux partout, depuis plus de 30 ans, bonne chance pour changer d'avis avec vos nouvelles idées enchevêtrées.
Code hérité - foo (barre de tableau []) sûr que vous pouvez utiliser une fonction toarray liste/vecteur/collection mais si vous n'utilisez aucune de ces fonctionnalités supplémentaires, il est plus facile d'utiliser un tableau pour commencer, souvent plus lisible sans changement de type.
Patron hérité - mon patron était un bon programmeur avant de se lancer dans la gestion il y a de nombreuses années et pense toujours qu'elle est à jour, "utiliser des tableaux" peut mettre fin à une réunion, expliquant ce qu'est une collection peut coûter à tout le monde un déjeuner.
1) Il n'y a pas de version multidimensionnelle de List. Si vos données ont plus d'une dimension, il sera très inefficace d'utiliser des listes.
2) Lorsque vous traitez avec un grand nombre de petits types de données (par exemple, une carte où tout ce que vous avez est d'un octet pour le type de terrain), il peut y avoir des différences de performances considérables en raison de la mise en cache. La version du tableau charge plusieurs éléments par mémoire lue, la version de liste n'en charge qu'un seul. De plus, la version de la baie contient plusieurs fois plus de cellules dans le cache que la version de la liste - si vous traitez les données à plusieurs reprises, cela peut faire une grande différence si la version de la baie tient dans le cache, mais pas la version de la liste.
Pour un cas extrême, considérez Minecraft. (Ouais, ce n'est pas écrit en C #. La même raison s'applique.)
Un tableau de 100 éléments d'un certain type T encapsule 100 variables indépendantes de type T. Si T se trouve être un type de valeur qui a un champ public mutable de type Q et un de type R, alors chaque élément du tableau encapsulera des variables indépendantes des types Q et R. L'ensemble du tableau encapsulera ainsi 100 variables indépendantes de type Q et 100 variables indépendantes de type R; chacune de ces variables peut être consultée individuellement sans affecter aucune autre. Aucun type de collection autre que les tableaux ne peut permettre aux champs de structures d'être utilisés comme variables indépendantes.
Si T se trouvait à la place être un type de classe avec des champs publics mutables de type Q et R, chaque élément du tableau contient la référence niquement, n'importe où dans l'univers, à une instance de T
, et si aucun des éléments du tableau ne sera jamais modifié pour identifier un objet auquel existe une référence extérieure, alors le tableau encapsulera efficacement 100 variables indépendantes de type Q et 100 variables indépendantes de type R. D'autres types de collection peuvent imiter le comportement d'un tel tableau, mais si le seul but du tableau est d'encapsuler 100 variables de type Q et 100 de type R, l'encapsulation de chaque paire de variables dans son propre objet de classe est un moyen coûteux de le faire. De plus, l'utilisation d'un tableau ou d'une collection de types de classes mutables crée la possibilité que les variables identifiées par les éléments du tableau ne soient pas indépendantes.
Si un type est censé se comporter comme une sorte d'objet, il doit s'agir d'un type de classe ou d'une structure de champ privé qui ne fournit aucun autre moyen de mutation que le remplacement. Si, cependant, un type est censé se comporter comme un tas de variables liées mais indépendantes collées ensemble avec du ruban adhésif, alors on devrait utiliser un type qui est un tas de variables collées avec du ruban adhésif --une structure de champ exposé. Les tableaux de ces types sont très efficaces pour travailler avec, et ont une sémantique très propre. L'utilisation de tout autre type entraînera une sémantique confuse, des performances inférieures ou les deux.
Une différence importante est l'allocation de mémoire. Par exemple, parcourir une liste chaînée peut entraîner de nombreux échecs de cache et des performances plus lentes, tandis qu'un tableau représente un morceau de mémoire contigu contenant plusieurs instances d'un type de données particulier, et le faire dans l'ordre est plus susceptible de toucher le processeur cache.
Bien sûr, un tableau de références d'objets peut ne pas bénéficier autant des accès au cache, car le déréférencement peut toujours vous emmener n'importe où en mémoire.
Ensuite, il existe des implémentations de liste telles que ArrayList, qui implémentent une liste à l'aide d'un tableau. Ce sont des primitives utiles à avoir.