Est-il préférable d'appeler ToList () ou ToArray () dans les requêtes LINQ?

519

Je rencontre souvent le cas où je veux évaluer une requête là où je la déclare. C'est généralement parce que j'ai besoin de répéter plusieurs fois et que le calcul est coûteux. Par exemple:

string raw = "...";
var lines = (from l in raw.Split('\n')
             let ll = l.Trim()
             where !string.IsNullOrEmpty(ll)
             select ll).ToList();

Cela fonctionne bien. Mais si je ne vais pas modifier le résultat, je ferais aussi bien d'appeler à la ToArray()place de ToList().

Je me demande cependant si ToArray() est implémenté par le premier appel ToList()et est donc moins efficace en mémoire que le simple appel ToList().

Suis-je fou? Dois-je simplement appelerToArray() - sûr et sécurisé en sachant que la mémoire ne sera pas allouée deux fois?

Frank Krueger
la source
10
Si vous voulez savoir ce qui se passe derrière les rideaux dans .NET, je recommande vraiment .NET Reflector
David Hedlund
32
@DavidHedlund Je recommande le code source .net .
Gqqnbig
1
Je ne suis pas d'accord que stackoverflow.com/questions/6750447/c-toarray-performance est un double de cette question même s'il existe une relation importante. L'utilisation de la mémoire (cette question) et la performance (autre question) sont des considérations intéressantes et non triviales. Ils peuvent être décrits séparément, mais les deux devraient prendre en compte la décision de choisir l'un plutôt que l'autre. Je ne peux recommander aucune des réponses à cette question ou à l'autre question comme étant complètes. Il y a plusieurs réponses qui, prises ensemble, fournissent une discussion assez complète sur la façon de choisir l'une plutôt que l'autre.
steve
1
@Gqqnbig - le commentaire le plus utile de tous les temps! Merci :-)
Mark Cooper

Réponses:

366

Sauf si vous avez simplement besoin d'un tableau pour répondre à d'autres contraintes que vous devez utiliser ToList. Dans la majorité des scénarios, ToArrayil allouera plus de mémoire que ToList.

Les deux utilisent des tableaux pour le stockage, mais ToListont une contrainte plus flexible. Il a besoin que le tableau soit au moins aussi grand que le nombre d'éléments dans la collection. Si le tableau est plus grand, ce n'est pas un problème. Cependant ToArray, le tableau doit être dimensionné exactement au nombre d'éléments.

Répondre à cette contrainte fait ToArraysouvent une allocation de plus que ToList. Une fois qu'il a un tableau suffisamment grand, il alloue un tableau qui a exactement la bonne taille et copie les éléments dans ce tableau. La seule fois où il peut éviter cela, c'est lorsque l'algorithme de croissance du tableau coïncide avec le nombre d'éléments devant être stockés (certainement dans la minorité).

ÉDITER

Quelques personnes m'ont interrogé sur les conséquences de la mémoire supplémentaire inutilisée dans le List<T> valeur.

C'est une préoccupation valable. Si la collection créée a une longue durée de vie, n'est jamais modifiée après avoir été créée et a de grandes chances d'atterrir dans le tas Gen2, alors vous feriez mieux de prendre l'allocation supplémentaire deToArray avance.

En général, je trouve que c'est le cas le plus rare. Il est beaucoup plus courant de voir un grand nombre d' ToArrayappels qui sont immédiatement transmis à d'autres utilisations de courte durée de la mémoire, auquel cas il ToListest manifestement préférable.

La clé ici est de profiler, profiler puis profiler un peu plus.

JaredPar
la source
14
D'un autre côté, la mémoire supplémentaire allouée pour les travaux de création de la baie ne serait-elle pas éligible pour le ramasse-miettes, alors que la surcharge supplémentaire pour la liste resterait? Je dis de garder les choses plus simples. Si vous devez ajouter ou supprimer des éléments, il existe un outil pour cela. Si vous ne le faites pas, il existe un outil différent pour cela. Utilisez celui qui a du sens. Si plus tard, vous découvrez un problème de mémoire et de performances, et c'est tout , changez-le.
Anthony Pegram
1
@AnthonyPegram oui, c'est une considération valable à faire. Si la valeur est utilisée dans un stockage à long terme, ne sera pas modifiée, et pourrait potentiellement arriver dans Gen 2, alors vous feriez mieux de payer l'allocation supplémentaire maintenant plutôt que de polluer le tas Gen 2. IME bien que je vois rarement cela. Il est beaucoup plus courant de voir ToArray être immédiatement transmis à une autre requête LINQ de courte durée.
JaredPar
2
@AnthonyPegram j'ai mis à jour ma réponse pour inclure ce côté de la discussion
JaredPar
8
@JaredPar Je ne comprends pas comment ToArraypeut allouer plus de mémoire s'il a besoin de la taille exacte des emplacements où ToList<>il y a évidemment ses emplacements de rechange automatiques. (auto
augmentation
5
@RoyiNamir parce que ToArray effectue d'abord les allocations de style ToList avec surcharge, puis effectue une allocation de taille exacte supplémentaire.
Timbo le
169

La différence de performance sera insignifiante, car List<T> est implémentée en tant que tableau de taille dynamique. Appelant soit ToArray()(qui utilise une Buffer<T>classe interne pour agrandir le tableau) soit ToList()(qui appelle leList<T>(IEnumerable<T>) constructeur) finira par les placer dans un tableau et agrandir le tableau jusqu'à ce qu'il convienne à tous.

Si vous souhaitez une confirmation concrète de ce fait, consultez la mise en œuvre des méthodes en question dans Reflector - vous verrez qu'elles se résument à un code presque identique.

mqp
la source
2
Un fait intéressant que j'ai rencontré est que pour les requêtes corrélées causées par l'utilisation d'un groupe défini via une jointure de groupe dans votre projection, Linq to SQL ajoute une autre sous-requête pour récupérer le nombre de ce groupe. Je suppose que cela signifie que dans ces cas, la taille de la collection sera connue avant que les éléments ne soient récupérés et donc un tableau de taille exacte pourrait être créé directement, ce qui permettrait d'économiser sur les ressources de traitement et de mémoire tout en matérialisant les résultats.
jpierson
133
Si le nombre est connu à l'avance, les performances sont identiques. Cependant, si le nombre n'est pas connu à l'avance, la seule différence entre ToArray()et ToList()est que le premier doit couper l'excédent, ce qui implique de copier l'ensemble du tableau, tandis que le second ne coupe pas l'excédent, mais utilise une moyenne de 25 % plus de mémoire. Cela n'aura des implications que si le type de données est grand struct. Juste matière à réflexion.
Scott Rippey
9
@EldritchConundrum 25% provient de cette logique: si le nombre d'éléments est inconnu, alors l'appel ToListou ToArraycommencera par la création d'un petit tampon. Lorsque ce tampon est rempli, il double la capacité du tampon et continue. La capacité étant toujours doublée, le tampon inutilisé sera toujours compris entre 0% et 50%.
Scott Rippey
2
@ScottRippey Je viens de rechercher la source de la nouvelle liste à partir de la source IEnumerable, et il vérifie si IEnumerable est une ICollection, et si c'est le cas, il commence par allouer un tableau avec la taille exacte nécessaire à partir de la propriété Count, donc ceci serait le cas où ToList () serait certainement plus rapide. Une réponse complète pourrait inclure ce fait, bien que je ne pense pas que ce soit le cas le plus courant.
AndyClaw
3
@AndyClaw Both Listet Buffervérifiera ICollection, auquel cas les performances seront identiques.
Scott Rippey
54

(sept ans plus tard ...)

Quelques autres (bonnes) réponses se sont concentrées sur les différences de performances microscopiques qui se produiront.

Cet article n'est qu'un complément pour mentionner la différence sémantique qui existe entre le IEnumerator<T>produit par un tableau ( T[]) et celui renvoyé par a List<T>.

Mieux illustré par l'exemple:

IList<int> source = Enumerable.Range(1, 10).ToArray();  // try changing to .ToList()

foreach (var x in source)
{
  if (x == 5)
    source[8] *= 100;
  Console.WriteLine(x);
}

Le code ci-dessus s'exécutera sans exception et produira la sortie:

1
2
3
4
5
6
7
8
900
dix

Cela montre que le IEnumarator<int>retourné par un int[]ne garde pas de trace si le tableau a été modifié depuis la création de l'énumérateur.

Notez que j'ai déclaré la variable locale en sourcetant que IList<int>. De cette façon, je m'assure que le compilateur C # n'optimise pas l' foreachinstruction en quelque chose qui équivaut à une for (var idx = 0; idx < source.Length; idx++) { /* ... */ }boucle. C'est quelque chose que le compilateur C # pourrait faire si j'utilise à la var source = ...;place. Dans ma version actuelle du framework .NET, l'énumérateur réel utilisé ici est un type de référence non public, System.SZArrayHelper+SZGenericArrayEnumerator`1[System.Int32]mais bien sûr, c'est un détail d'implémentation.

Maintenant, si je change .ToArray()en .ToList(), je reçois seulement:

1
2
3
4
5

suivi d'un System.InvalidOperationExceptionéclatement disant:

La collection a été modifiée; L'opération d'énumération peut ne pas s'exécuter.

L'énumérateur sous-jacent dans ce cas est le type de valeur mutable public System.Collections.Generic.List`1+Enumerator[System.Int32](encadré dans une IEnumerator<int>boîte dans ce cas parce que j'utilise IList<int>).

En conclusion, l'énumérateur produit par unList<T>garde la trace de la modification de la liste pendant l'énumération, alors que l'énumérateur produit parT[]ne le fait pas. Considérez donc cette différence lors du choix entre.ToList()et.ToArray().

Les gens en ajoutent souvent un supplémentaire .ToArray() ou .ToList()contournent une collection qui garde la trace de sa modification pendant la durée de vie d'un enquêteur.

(Si quelqu'un veut savoir comment le List<>garde trace si la collection a été modifiée, il y a un champ privé _versiondans cette classe qui est changé à chaque List<>mise à jour.)

Jeppe Stig Nielsen
la source
28

Je suis d'accord avec @mquander que la différence de performances devrait être insignifiante. Cependant, je voulais le comparer pour être sûr, alors je l'ai fait - et c'est insignifiant.

Testing with List<T> source:
ToArray time: 1934 ms (0.01934 ms/call), memory used: 4021 bytes/array
ToList  time: 1902 ms (0.01902 ms/call), memory used: 4045 bytes/List

Testing with array source:
ToArray time: 1957 ms (0.01957 ms/call), memory used: 4021 bytes/array
ToList  time: 2022 ms (0.02022 ms/call), memory used: 4045 bytes/List

Chaque tableau / liste source avait 1 000 éléments. Vous pouvez donc voir que les différences de temps et de mémoire sont négligeables.

Ma conclusion: vous pourriez aussi bien utiliser ToList () , car a List<T>fournit plus de fonctionnalités qu'un tableau, à moins que quelques octets de mémoire ne vous importent vraiment.

EMP
la source
1
Je me demande si ce résultat serait différent si vous utilisiez un grand structau lieu d'un type ou d'une classe primitive.
Scott Rippey
12
Liste <T> .ToList ???? Quel sens ? Il vaut mieux essayer d'y ajouter un IEnumerable, qui n'implémente pas l'interface ICollection.
Grigory
8
Je voulais vous assurer que je ne mesure que le temps du ToListou ToArrayappel et non l'énumération de tout IEnumerable. List <T> .ToList () crée toujours une nouvelle List <T> - elle ne se contente pas de "retourner ceci".
EMP
23
-1 Les comportements de ToArray()et ToList()diffèrent trop lorsqu'ils sont fournis avec un ICollection<T>paramètre - Ils ne font qu'une seule allocation et une seule opération de copie. Les deux List<T>et Arrayimplémentent ICollection<T>, donc vos repères ne sont pas du tout valides.
Mohammad Dehghan
1
Pour toute personne intéressée, j'ai publié ma propre référence en tant que réponse distincte . Il utilise .Select(i => i)pour éviter le ICollection<T>problème d'implémentation et inclut un groupe de contrôle pour voir combien de temps est simplement pris en itérant sur la source IEnumerable<>en premier lieu.
StriplingWarrior
19

ToList()est généralement préférable si vous l'utilisez IEnumerable<T>(depuis ORM, par exemple). Si la longueur de la séquence n'est pas connue au début, ToArray()crée une collection de longueur dynamique comme List, puis la convertit en tableau, ce qui prend plus de temps.

Vitaliy Ulantikov
la source
26
J'ai décidé que la lisibilité l'emporte sur les performances dans ce cas. J'utilise maintenant ToList uniquement lorsque je m'attends à continuer d'ajouter des éléments. Dans tous les autres cas (la plupart des cas), j'utilise ToArray. Mais merci pour la contribution!
Frank Krueger
5
Recherche dans ILSpy, Enumerable.ToArray()appels new Buffer<TSource>(source).ToArray(). Dans le constructeur Buffer si la source implémente ICollection, elle appelle alors source.CopyTo (items, 0), puis .ToArray () renvoie directement le tableau des éléments internes. Il n'y a donc aucune conversion qui prend du temps supplémentaire dans ce cas. Si la source n'implémente pas ICollection, le ToArray entraînera une copie du tableau afin de supprimer les emplacements supplémentaires inutilisés à la fin du tableau, comme décrit par le commentaire de Scott Rippey ci-dessus.
BrandonAGr
19

La mémoire sera toujours allouée deux fois - ou quelque chose de proche. Comme vous ne pouvez pas redimensionner un tableau, les deux méthodes utiliseront une sorte de mécanisme pour collecter les données dans une collection croissante. (Eh bien, la Liste est une collection croissante en soi.)

La liste utilise une baie comme stockage interne et double la capacité en cas de besoin. Cela signifie qu'en moyenne 2/3 des articles ont été réalloués au moins une fois, la moitié de ceux réalloués au moins deux fois, la moitié de ceux au moins trois fois, et ainsi de suite. Cela signifie que chaque élément a été en moyenne réalloué 1,3 fois, ce qui n'est pas beaucoup de frais généraux.

N'oubliez pas également que si vous collectez des chaînes, la collection elle-même ne contient que les références aux chaînes, les chaînes elles-mêmes ne sont pas réallouées.

Guffa
la source
Cela peut être une question ignorante à poser, mais la logique 2/3, 1/3, 1/6 que vous décrivez ne suppose-t-elle pas que le tableau de la liste peut être étendu en place? Autrement dit, il y a de l'espace libre à la fin du tableau de sorte que l'allocation existante n'a pas besoin d'être déplacée?
@JonofAllTrades: Non, la baie n'est jamais étendue en place, la gestion de la mémoire dans .NET ne fait tout simplement pas cela. S'il était prolongé, il ne serait pas nécessaire de réaffecter les articles.
Guffa
Ah, je vois: les articles qui ne sont pas réalloués ne devaient pas le faire parce qu'ils étaient dans l'allocation finale. Tous les éléments alloués dans les allocations précédentes sont déplacés, mais en raison des augmentations logarithmiques de la longueur du tableau, il s'agit d'une fraction calculable. Merci de clarifier!
19

Nous sommes en 2020 à l'extérieur et tout le monde utilise .NET Core 3.1, j'ai donc décidé d'exécuter des benchmarks avec Benchmark.NET.

TL; DR: ToArray () est meilleur en termes de performances et fait une meilleure intention de transmission de travail si vous ne prévoyez pas de muter la collection.


    [MemoryDiagnoser]
    public class Benchmarks
    {
        [Params(0, 1, 6, 10, 39, 100, 666, 1000, 1337, 10000)]
        public int Count { get; set; }

        public IEnumerable<int> Items => Enumerable.Range(0, Count);

        [Benchmark(Description = "ToArray()", Baseline = true)]
        public int[] ToArray() => Items.ToArray();

        [Benchmark(Description = "ToList()")]
        public List<int> ToList() => Items.ToList();

        public static void Main() => BenchmarkRunner.Run<Benchmarks>();
    }

Les résultats sont:


    BenchmarkDotNet=v0.12.0, OS=Windows 10.0.14393.3443 (1607/AnniversaryUpdate/Redstone1)
    Intel Core i5-4460 CPU 3.20GHz (Haswell), 1 CPU, 4 logical and 4 physical cores
    Frequency=3124994 Hz, Resolution=320.0006 ns, Timer=TSC
    .NET Core SDK=3.1.100
      [Host]     : .NET Core 3.1.0 (CoreCLR 4.700.19.56402, CoreFX 4.700.19.56404), X64 RyuJIT
      DefaultJob : .NET Core 3.1.0 (CoreCLR 4.700.19.56402, CoreFX 4.700.19.56404), X64 RyuJIT


    |    Method | Count |          Mean |       Error |      StdDev |        Median | Ratio | RatioSD |   Gen 0 | Gen 1 | Gen 2 | Allocated |
    |---------- |------ |--------------:|------------:|------------:|--------------:|------:|--------:|--------:|------:|------:|----------:|
    | ToArray() |     0 |      7.357 ns |   0.2096 ns |   0.1960 ns |      7.323 ns |  1.00 |    0.00 |       - |     - |     - |         - |
    |  ToList() |     0 |     13.174 ns |   0.2094 ns |   0.1958 ns |     13.084 ns |  1.79 |    0.05 |  0.0102 |     - |     - |      32 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() |     1 |     23.917 ns |   0.4999 ns |   0.4676 ns |     23.954 ns |  1.00 |    0.00 |  0.0229 |     - |     - |      72 B |
    |  ToList() |     1 |     33.867 ns |   0.7350 ns |   0.6876 ns |     34.013 ns |  1.42 |    0.04 |  0.0331 |     - |     - |     104 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() |     6 |     28.242 ns |   0.5071 ns |   0.4234 ns |     28.196 ns |  1.00 |    0.00 |  0.0280 |     - |     - |      88 B |
    |  ToList() |     6 |     43.516 ns |   0.9448 ns |   1.1949 ns |     42.896 ns |  1.56 |    0.06 |  0.0382 |     - |     - |     120 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() |    10 |     31.636 ns |   0.5408 ns |   0.4516 ns |     31.657 ns |  1.00 |    0.00 |  0.0331 |     - |     - |     104 B |
    |  ToList() |    10 |     53.870 ns |   1.2988 ns |   2.2403 ns |     53.415 ns |  1.77 |    0.07 |  0.0433 |     - |     - |     136 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() |    39 |     58.896 ns |   0.9441 ns |   0.8369 ns |     58.548 ns |  1.00 |    0.00 |  0.0713 |     - |     - |     224 B |
    |  ToList() |    39 |    138.054 ns |   2.8185 ns |   3.2458 ns |    138.937 ns |  2.35 |    0.08 |  0.0815 |     - |     - |     256 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() |   100 |    119.167 ns |   1.6195 ns |   1.4357 ns |    119.120 ns |  1.00 |    0.00 |  0.1478 |     - |     - |     464 B |
    |  ToList() |   100 |    274.053 ns |   5.1073 ns |   4.7774 ns |    272.242 ns |  2.30 |    0.06 |  0.1578 |     - |     - |     496 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() |   666 |    569.920 ns |  11.4496 ns |  11.2450 ns |    571.647 ns |  1.00 |    0.00 |  0.8688 |     - |     - |    2728 B |
    |  ToList() |   666 |  1,621.752 ns |  17.1176 ns |  16.0118 ns |  1,623.566 ns |  2.85 |    0.05 |  0.8793 |     - |     - |    2760 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() |  1000 |    796.705 ns |  16.7091 ns |  19.8910 ns |    796.610 ns |  1.00 |    0.00 |  1.2951 |     - |     - |    4064 B |
    |  ToList() |  1000 |  2,453.110 ns |  48.1121 ns |  65.8563 ns |  2,460.190 ns |  3.09 |    0.10 |  1.3046 |     - |     - |    4096 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() |  1337 |  1,057.983 ns |  20.9810 ns |  41.4145 ns |  1,041.028 ns |  1.00 |    0.00 |  1.7223 |     - |     - |    5416 B |
    |  ToList() |  1337 |  3,217.550 ns |  62.3777 ns |  61.2633 ns |  3,203.928 ns |  2.98 |    0.13 |  1.7357 |     - |     - |    5448 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() | 10000 |  7,309.844 ns | 160.0343 ns | 141.8662 ns |  7,279.387 ns |  1.00 |    0.00 | 12.6572 |     - |     - |   40064 B |
    |  ToList() | 10000 | 23,858.032 ns | 389.6592 ns | 364.4874 ns | 23,759.001 ns |  3.26 |    0.08 | 12.6343 |     - |     - |   40096 B |

    // * Hints *
    Outliers
      Benchmarks.ToArray(): Default -> 2 outliers were removed (35.20 ns, 35.29 ns)
      Benchmarks.ToArray(): Default -> 2 outliers were removed (38.51 ns, 38.88 ns)
      Benchmarks.ToList(): Default  -> 1 outlier  was  removed (64.69 ns)
      Benchmarks.ToArray(): Default -> 1 outlier  was  removed (67.02 ns)
      Benchmarks.ToArray(): Default -> 1 outlier  was  removed (130.08 ns)
      Benchmarks.ToArray(): Default -> 1 outlier  was  detected (541.82 ns)
      Benchmarks.ToArray(): Default -> 1 outlier  was  removed (7.82 us)

    // * Legends *
      Count     : Value of the 'Count' parameter
      Mean      : Arithmetic mean of all measurements
      Error     : Half of 99.9% confidence interval
      StdDev    : Standard deviation of all measurements
      Median    : Value separating the higher half of all measurements (50th percentile)
      Ratio     : Mean of the ratio distribution ([Current]/[Baseline])
      RatioSD   : Standard deviation of the ratio distribution ([Current]/[Baseline])
      Gen 0     : GC Generation 0 collects per 1000 operations
      Gen 1     : GC Generation 1 collects per 1000 operations
      Gen 2     : GC Generation 2 collects per 1000 operations
      Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
      1 ns      : 1 Nanosecond (0.000000001 sec)
Tyrrrz
la source
1
Si vous ne prévoyez pas de muter la collection, je pense que l'intention peut mieux être affichée avec ToImmutableArray()(à partir du package System.Collections.Immutable) 😉
Arturo Torres Sánchez
@ ArturoTorresSánchez true, mais si la collection n'est pas exposée en dehors d'une méthode, j'utiliserais simplement un tableau.
Tyrrrz
2
Merci pour cela. La réponse choisie est un simple argument et suppose des résultats à la suite de cet argument. Pour le faire scientifiquement et en bonus, sachez combien il y a de différence, il n'y a qu'une seule vraie façon de savoir.
Jonas
15

Éditer : La dernière partie de cette réponse n'est pas valide. Cependant, le reste est toujours une information utile, donc je vais le laisser.

Je sais que c'est un vieux post, mais après avoir posé la même question et fait quelques recherches, j'ai trouvé quelque chose d'intéressant qui pourrait valoir la peine d'être partagé.

Tout d'abord, je suis d'accord avec @mquander et sa réponse. Il a raison de dire que sur le plan des performances, les deux sont identiques.

Cependant, j'ai utilisé Reflector pour jeter un œil aux méthodes dans l' System.Linq.Enumerableespace de noms des extensions, et j'ai remarqué une optimisation très courante.
Dans la mesure du possible, la IEnumerable<T>source est convertie en IList<T>ou ICollection<T>pour optimiser la méthode. Par exemple, regardezElementAt(int) .

Fait intéressant, Microsoft a choisi d'optimiser uniquement pour IList<T>, mais pas IList. Il semble que Microsoft préfère utiliser l' IList<T>interface.

System.Arrayimplémente uniquement IList, il ne bénéficiera donc d'aucune de ces optimisations d'extension.
Par conséquent, je soumets que la meilleure pratique consiste à utiliser la .ToList()méthode.
Si vous utilisez l'une des méthodes d'extension ou passez la liste à une autre méthode, il est possible qu'elle soit optimisée pour un IList<T>.

Scott Rippey
la source
16
J'ai fait un test et découvert quelque chose de surprenant. Un tableau implémente IList <T>! L'utilisation de Reflector pour analyser System.Array ne révèle qu'une chaîne d'héritage d'IList, ICollection, IEnumerable mais en utilisant la réflexion au moment de l'exécution, j'ai découvert que string [] a une chaîne d'héritage d'IList, ICollection, IEnumerable, IList <string>, ICollection <string >, IEnumerable <chaîne>. Par conséquent, je n'ai pas de meilleure réponse que @mquander!
Scott Rippey
@ScottRippey Oui. L'observation étrange que vous avez remarquée fait en fait partie d'un «hack» - et elle a aussi des implications assez étranges concernant la «taille fixe» et les propriétés similaires (avec quelques incohérences selon la façon dont vous la transformez). Il y a quelques commentaires assez importants touchant ce sujet à l'intérieur du code source .net. Désolé de ne pas avoir créé de lien, mais si je me souviens bien, il est assez facile à trouver (à l'intérieur de la classe array). (Et il y a aussi une grande question SO discutant des incohérences .... quelque part ...> __>)
AnorZaken
@ScottRippey juste FYI J'ai trouvé cette réponse qui a à voir avec votre commentaire: stackoverflow.com/a/4482567/2063755
David Klempfner
14

J'ai trouvé que les autres repères que les gens ont faits ici manquaient, alors voici ma fissure. Faites-moi savoir si vous trouvez quelque chose de mal avec ma méthodologie.

/* This is a benchmarking template I use in LINQPad when I want to do a
 * quick performance test. Just give it a couple of actions to test and
 * it will give you a pretty good idea of how long they take compared
 * to one another. It's not perfect: You can expect a 3% error margin
 * under ideal circumstances. But if you're not going to improve
 * performance by more than 3%, you probably don't care anyway.*/
void Main()
{
    // Enter setup code here
    var values = Enumerable.Range(1, 100000)
        .Select(i => i.ToString())
        .ToArray()
        .Select(i => i);
    values.GetType().Dump();
    var actions = new[]
    {
        new TimedAction("ToList", () =>
        {
            values.ToList();
        }),
        new TimedAction("ToArray", () =>
        {
            values.ToArray();
        }),
        new TimedAction("Control", () =>
        {
            foreach (var element in values)
            {
                // do nothing
            }
        }),
        // Add tests as desired
    };
    const int TimesToRun = 1000; // Tweak this as necessary
    TimeActions(TimesToRun, actions);
}


#region timer helper methods
// Define other methods and classes here
public void TimeActions(int iterations, params TimedAction[] actions)
{
    Stopwatch s = new Stopwatch();
    int length = actions.Length;
    var results = new ActionResult[actions.Length];
    // Perform the actions in their initial order.
    for (int i = 0; i < length; i++)
    {
        var action = actions[i];
        var result = results[i] = new ActionResult { Message = action.Message };
        // Do a dry run to get things ramped up/cached
        result.DryRun1 = s.Time(action.Action, 10);
        result.FullRun1 = s.Time(action.Action, iterations);
    }
    // Perform the actions in reverse order.
    for (int i = length - 1; i >= 0; i--)
    {
        var action = actions[i];
        var result = results[i];
        // Do a dry run to get things ramped up/cached
        result.DryRun2 = s.Time(action.Action, 10);
        result.FullRun2 = s.Time(action.Action, iterations);
    }
    results.Dump();
}

public class ActionResult
{
    public string Message { get; set; }
    public double DryRun1 { get; set; }
    public double DryRun2 { get; set; }
    public double FullRun1 { get; set; }
    public double FullRun2 { get; set; }
}

public class TimedAction
{
    public TimedAction(string message, Action action)
    {
        Message = message;
        Action = action;
    }
    public string Message { get; private set; }
    public Action Action { get; private set; }
}

public static class StopwatchExtensions
{
    public static double Time(this Stopwatch sw, Action action, int iterations)
    {
        sw.Restart();
        for (int i = 0; i < iterations; i++)
        {
            action();
        }
        sw.Stop();

        return sw.Elapsed.TotalMilliseconds;
    }
}
#endregion

Vous pouvez télécharger le script LINQPad ici .

Résultats: Performances ToArray vs ToList

En ajustant le code ci-dessus, vous découvrirez que:

  1. La différence est moins importante lorsqu'il s'agit de petits tableaux . Plus d'itérations, mais des tableaux plus petits
  2. La différence est moins importante lorsqu'il s'agit de ints plutôt que destring art.
  3. L'utilisation de grands structs au lieu de strings prend généralement beaucoup plus de temps, mais ne change pas vraiment le rapport.

Cela rejoint les conclusions des réponses les mieux notées:

  1. Il est peu probable que vous remarquiez une différence de performances, sauf si votre code produit fréquemment de nombreuses grandes listes de données. (Il n'y avait qu'une différence de 200 ms lors de la création de 1000 listes de 100K cordes chacune.)
  2. ToList() fonctionne toujours plus rapidement et serait un meilleur choix si vous ne prévoyez pas de vous accrocher aux résultats pendant longtemps.

Mise à jour

@JonHanna a souligné qu'en fonction de l'implémentation de, Selectil est possible pour a ToList()ou l' ToArray()implémentation de prédire à l'avance la taille de la collection résultante. Remplacer .Select(i => i)dans le code ci-dessus avec Where(i => true) des résultats très similaires pour le moment, et est plus susceptible de le faire quelle que soit l'implémentation .NET.

Benchmark utilisant Where au lieu de Select

StriplingWarrior
la source
Dans .NET Core, les deux cas devraient être meilleurs ici que sur netfx, car il se rendra compte que la taille va être 100000et l'utilisera pour optimiser les deux ToList()et ToArray(), ToArray()étant très légèrement plus léger car il n'a pas besoin de l'opération de réduction dont il aurait besoin sinon, qui est le seul endroit ToList()a l'avantage. L'exemple dans la question serait toujours perdu, car les Wheremoyens d'une telle prédiction de taille ne peuvent pas être faits.
Jon Hanna
@ JonHanna: Merci pour la rétroaction rapide. Je ne savais pas que .NET Core faisait cette optimisation. C'est super. Dans mon code, .Select(i => i)pourrait être remplacé par .Where(i => true)pour corriger cela.
StriplingWarrior
Oui, cela arrêterait l'optimisation qui l'affecterait sur corefx. Il pourrait être intéressant d'avoir à la fois une taille qui est une puissance de deux (ce qui devrait donner ToArray()un avantage) et une autre qui ne l'est pas, comme ci-dessus, et de comparer les résultats.
Jon Hanna
@ JonHanna: Fait intéressant, ToArray() perd toujours dans le meilleur des cas. Avec les Math.Pow(2, 15)éléments, c'est (ToList: 700ms, ToArray: 900ms). L'ajout d'un élément supplémentaire le renverse (ToList: 925, ToArray: 1350). Je me demande si ToArrayest toujours en train de copier la matrice même si elle est déjà à la taille parfaite? Ils ont probablement pensé que c'était un événement assez rare pour que cela ne valait pas la peine supplémentaire conditionnelle.
StriplingWarrior
Il n'a pas copié sur la taille exacte, même avant que nous commencions à l'optimiser dans corefx, c'est donc le cas où il obtient le plus de pauses.
Jon Hanna
12

Vous devez baser votre décision sur ToListou en ToArrayfonction de ce que, idéalement, le choix de conception est. Si vous voulez une collection qui ne peut être itérée et accessible que par index, choisissez ToArray. Si vous souhaitez des fonctionnalités supplémentaires d'ajout et de suppression de la collection plus tard sans trop de tracas, faites unToList (pas vraiment que vous ne pouvez pas ajouter à un tableau, mais ce n'est généralement pas le bon outil pour cela).

Si les performances sont importantes, vous devez également envisager ce qui serait plus rapide à utiliser. De façon réaliste, vous n'appelerez pas ToListou ToArrayun million de fois, mais vous pourriez travailler sur la collection obtenue un million de fois. À cet égard, []c'est mieux, car List<>c'est []avec des frais généraux. Voir ce fil pour une comparaison d'efficacité: Lequel est le plus efficace: List <int> or int []

Dans mes propres tests il y a quelque temps, j'avais trouvé ToArrayplus vite. Et je ne sais pas à quel point les tests étaient biaisés. La différence de performances est cependant insignifiante, ce qui ne peut être perceptible que si vous exécutez ces requêtes dans une boucle des millions de fois.

nawfal
la source
2
Oui - si le compilateur sait que vous parcourez un tableau (plutôt qu'un IEnumerable <>), il peut optimiser l'itération de manière significative.
RobSiklos
12

Une réponse très tardive mais je pense que ce sera utile pour les googleurs.

Ils sucent tous les deux lorsqu'ils ont créé avec linq. Ils implémentent tous les deux le même code pour redimensionner le tampon si nécessaire . ToArrayutilise en interne une classe pour convertir IEnumerable<>en tableau, en allouant un tableau de 4 éléments. Si cela ne suffit pas, cela double la taille en créant un nouveau tableau double la taille du tableau actuel et en copiant le tableau actuel. À la fin, il alloue un nouveau tableau de comptage de vos articles. Si votre requête retourne 129 éléments, ToArray effectuera 6 allocations et opérations de copie de mémoire pour créer un tableau de 256 éléments et puis un autre tableau de 129 pour retourner. tant pour l'efficacité de la mémoire.

ToList fait la même chose, mais il ignore la dernière allocation puisque vous pouvez ajouter des éléments à l'avenir. La liste ne se soucie pas si elle est créée à partir d'une requête linq ou créée manuellement.

pour la création List est meilleure avec la mémoire mais pire avec cpu car list est une solution générique chaque action nécessite des vérifications de plage supplémentaires aux vérifications de plage interne du .net pour les tableaux.

Donc, si vous parcourez votre jeu de résultats trop de fois, les tableaux sont bons, car cela signifie moins de vérifications de plage que les listes, et les compilateurs optimisent généralement les tableaux pour un accès séquentiel.

L'allocation d'initialisation de la liste peut être meilleure si vous spécifiez le paramètre de capacité lorsque vous le créez. Dans ce cas, il n'allouera le tableau qu'une seule fois, en supposant que vous connaissez la taille du résultat. ToListde linq ne spécifie pas de surcharge pour le fournir, nous devons donc créer notre méthode d'extension qui crée une liste avec une capacité donnée, puis utilise List<>.AddRange.

Pour terminer cette réponse, je dois écrire les phrases suivantes

  1. À la fin, vous pouvez utiliser un ToArray ou ToList, les performances ne seront pas si différentes (voir la réponse de @EMP).
  2. Vous utilisez C #. Si vous avez besoin de performances, ne vous inquiétez pas d'écrire sur du code haute performance, mais ne vous inquiétez pas d'écrire du code de mauvaise performance.
  3. Ciblez toujours x64 pour du code haute performance. AFAIK, x64 JIT est basé sur le compilateur C ++ et fait des choses amusantes comme des optimisations de récursivité de queue.
  4. Avec la version 4.5, vous pouvez également profiter de l'optimisation guidée par profil et du JIT multicœur.
  5. Enfin, vous pouvez utiliser le modèle async / attente pour le traiter plus rapidement.
Erdogan Kurtur
la source
Ils sucent tous les deux? Avez-vous une autre idée qui ne nécessite pas d'allocation de mémoire redondante?
nawfal
Dans le contexte de la question, oui, ils craignent tous les deux, mais à cause d'allocations redondantes, et rien d'autre. Pour réduire l'allocation redondante, on peut utiliser des listes liées au détriment de la mémoire et de la vitesse d'itération. Au bout du compte, c'est ce que nous faisons, nous faisons des compromis. Une autre idée est de créer une liste d'une capacité de 200 (par exemple) puis de charger des éléments. Cela réduira également la redondance, mais les tableaux sont toujours plus rapides, c'est donc un autre compromis.
Erdogan Kurtur
Créer une liste de 200 ? Cela pourrait éviter de redimensionner, mais je parlais de la mémoire redondante utilisée. Vous ne pouvez pas l'aider car il n'y a aucune connaissance préalable de ce que pourrait être la taille. Vous pouvez déjà spécifier la capacité dans le constructeur d'un List<T>, mais quand vous ne le faites pas ou quand vous ne le pouvez pas, vous ne pouvez pas l'aider.
nawfal
2
les seules données redondantes en mémoire sont le contenu du tableau qui est une liste de pointeurs (dans ce cas). un million de pointeurs 64 bits prend jusqu'à 8 Mo de mémoire, ce qui n'est rien comparé à un million d'objets vers lesquels ils pointent. 200 n'est qu'un nombre, et il a une chance de réduire le nombre d'appels de redimensionnement au maximum 5 fois. et oui, on ne peut pas s'en empêcher. nous n'avons pas de meilleures options. Je n'ai pas de meilleure solution, mais cela ne signifie pas que je n'ai pas le droit de dire où est le problème.
Erdogan Kurtur du
1
hmm à la fin, c'est là que vous tracez la ligne. J'aime l'implémentation actuelle. Le ton de votre réponse m'a fait penser que c'était de la critique plutôt que de savoir où était le problème :)
nawfal
7

C'est une vieille question - mais pour le bénéfice des utilisateurs qui y tombent, il existe également une alternative à la `` mémorisation '' de l'énumérable - qui a pour effet de mettre en cache et d'arrêter l'énumération multiple d'une instruction Linq, ce que ToArray () et ToList () sont beaucoup utilisés, même si les attributs de collection de la liste ou du tableau ne sont jamais utilisés.

Memoize est disponible dans la bibliothèque RX / System.Interactive et est expliqué ici: Plus de LINQ avec System.Interactive

(Du blog de Bart De'Smet qui est une lecture fortement recommandée si vous travaillez beaucoup avec Linq to Objects)

Frep D-Oronge
la source
4

Une option consiste à ajouter votre propre méthode d'extension qui renvoie une lecture seule ICollection<T> . Cela peut être mieux que d'utiliser ToListou ToArraylorsque vous ne souhaitez pas utiliser les propriétés d'indexation d'un tableau / liste ou ajouter / supprimer d'une liste.

public static class EnumerableExtension
{
    /// <summary>
    /// Causes immediate evaluation of the linq but only if required.
    /// As it returns a readonly ICollection, is better than using ToList or ToArray
    /// when you do not want to use the indexing properties of an IList, or add to the collection.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="enumerable"></param>
    /// <returns>Readonly collection</returns>
    public static ICollection<T> Evaluate<T>(this IEnumerable<T> enumerable)
    {
        //if it's already a readonly collection, use it
        var collection = enumerable as ICollection<T>;
        if ((collection != null) && collection.IsReadOnly)
        {
            return collection;
        }
        //or make a new collection
        return enumerable.ToList().AsReadOnly();
    }
}

Tests unitaires:

[TestClass]
public sealed class EvaluateLinqTests
{
    [TestMethod]
    public void EvalTest()
    {
        var list = new List<int> {1, 2, 3};
        var linqResult = list.Select(i => i);
        var linqResultEvaluated = list.Select(i => i).Evaluate();
        list.Clear();
        Assert.AreEqual(0, linqResult.Count());
        //even though we have cleared the underlying list, the evaluated list does not change
        Assert.AreEqual(3, linqResultEvaluated.Count());
    }

    [TestMethod]
    public void DoesNotSaveCreatingListWhenHasListTest()
    {
        var list = new List<int> {1, 2, 3};
        var linqResultEvaluated = list.Evaluate();
        //list is not readonly, so we expect a new list
        Assert.AreNotSame(list, linqResultEvaluated);
    }

    [TestMethod]
    public void SavesCreatingListWhenHasReadonlyListTest()
    {
        var list = new List<int> {1, 2, 3}.AsReadOnly();
        var linqResultEvaluated = list.Evaluate();
        //list is readonly, so we don't expect a new list
        Assert.AreSame(list, linqResultEvaluated);
    }

    [TestMethod]
    public void SavesCreatingListWhenHasArrayTest()
    {
        var list = new[] {1, 2, 3};
        var linqResultEvaluated = list.Evaluate();
        //arrays are readonly (wrt ICollection<T> interface), so we don't expect a new object
        Assert.AreSame(list, linqResultEvaluated);
    }

    [TestMethod]
    [ExpectedException(typeof (NotSupportedException))]
    public void CantAddToResultTest()
    {
        var list = new List<int> {1, 2, 3};
        var linqResultEvaluated = list.Evaluate();
        Assert.AreNotSame(list, linqResultEvaluated);
        linqResultEvaluated.Add(4);
    }

    [TestMethod]
    [ExpectedException(typeof (NotSupportedException))]
    public void CantRemoveFromResultTest()
    {
        var list = new List<int> {1, 2, 3};
        var linqResultEvaluated = list.Evaluate();
        Assert.AreNotSame(list, linqResultEvaluated);
        linqResultEvaluated.Remove(1);
    }
}
Weston
la source
Il convient de noter que le contrat de collecte en lecture seule stipule uniquement que l'utilisateur de l'objet ne peut pas le modifier, mais le propriétaire peut toujours le faire s'il conserve une référence à celui-ci qui offre une interface mutable. Pour les interfaces qui garantissent que la structure sous-jacente ne changera jamais, regardez les collections immuables. Quant à savoir pourquoi les collections immuables, en lecture seule ou en lecture-écriture simples sont meilleures ou pires, il faut un point de référence pour la comparaison; il n'y a pas de réponse ultime (sinon nous n'aurions pas à choisir).
TNE
@tne Remarque Je fais Tolist avant AsReadOnly, donc il n'y a aucune référence au mutable sous-jacent.
weston
Vous avez tout à fait raison, et c'était probablement la meilleure façon de faire les choses avant que les collections immuables n'arrivent sur la BCL (je vois que la première version bêta est sortie un mois après votre réponse).
TNE
Des collections immuables existent pour la sécurité des threads, où les threads peuvent supposer que cela ne changera pas, et si c'est le cas, une nouvelle version est créée, au lieu de courir contre les lecteurs et de la changer pendant qu'ils l'utilisent. De cette façon, personne n'a jamais besoin d'acquérir une serrure.
doug65536
4

ToListAsync<T>() est préféré.

Dans Entity Framework 6, les deux méthodes finissent par appeler la même méthode interne, mais les ToArrayAsync<T>()appels list.ToArray()à la fin, qui sont implémentés comme

T[] array = new T[_size];
Array.Copy(_items, 0, array, 0, _size);
return array;

Il en ToArrayAsync<T>()est de même pour certains frais généraux, ce qui ToListAsync<T>()est préférable.

Stephen Zeng
la source
1
C'est en fait la réponse que je cherchais, comment EF le fait. Je serais curieux de savoir comment cela se passe dans EF Core.
Shimmy Weitzhandler
3

Vieille question mais nouveaux questionneurs en tout temps.

Selon la source de System.Linq.Enumerable , il ToListsuffit de renvoyer a new List(source), tout en ToArrayutilisant a new Buffer<T>(source).ToArray()pour renvoyer a T[].

À propos de l'allocation de mémoire:

Lors de l'exécution sur un IEnumerable<T>seul objet, ToArrayallouez de la mémoire une fois de plus ToList. Mais vous n'avez pas à vous en soucier dans la plupart des cas, car GC effectuera la collecte des ordures en cas de besoin.

À propos de l'exécution efficace:

Ceux qui posent cette question peuvent exécuter le code suivant sur votre propre machine et vous obtiendrez votre réponse.

class PersonC
{
    public Guid uuid;
    public string name;
    public int age;
    public bool sex;
    public DateTime BirthDay;
    public double weight;
}

struct PersonS
{
    public Guid uuid;
    public string name;
    public int age;
    public bool sex;
    public DateTime BirthDay;
    public double weight;
}

class PersonT<T> : IEnumerable<T>
{
    private List<T> items;
    public PersonT(IEnumerable<T> init)
    {
        items = new List<T>(init);
    }

    public IEnumerator<T> GetEnumerator() => items.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => items.GetEnumerator();
}

private IEnumerable<PersonC> C(int count)
{
    for (var i = 0; i < count; ++i)
    {
        var guid = Guid.NewGuid();
        var guidBytes = guid.ToByteArray(); //16 bytes
        yield return new PersonC
        {
            uuid = guid,
            name = guid.ToString(),
            age = guidBytes[0] ^ guidBytes[7],
            sex = guidBytes[14] % 2 == 0,
            BirthDay = DateTime.Now.AddDays(-guidBytes[11] * 18),
            weight = guidBytes[12] * 100
        };
    }
}

private IEnumerable<PersonS> S(int count)
{
    for (var i = 0; i < count; ++i)
    {
        var guid = Guid.NewGuid();
        var guidBytes = guid.ToByteArray(); //16 bytes
        yield return new PersonS
        {
            uuid = guid,
            name = guid.ToString(),
            age = guidBytes[0] ^ guidBytes[7],
            sex = guidBytes[14] % 2 == 0,
            BirthDay = DateTime.Now.AddDays(-guidBytes[11] * 18),
            weight = guidBytes[12] * 100
        };
    }
}

private void MakeLog(string test, List<long> log) =>
    Console.WriteLine("{0} {1} ms -> [{2}]",
        test,
        log.Average(),
        string.Join(", ", log)
    );

private void Test1(int times, int count)
{
    var test = Enumerable.Range(1, times).ToArray();

    MakeLog("C.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = C(count).ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("C.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = C(count).ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = S(count).ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = S(count).ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());
}

private void Test2(int times, int count)
{
    var test = Enumerable.Range(1, times).ToArray();

    var dataC1 = new PersonT<PersonC>(C(count));
    var dataS1 = new PersonT<PersonS>(S(count));

    MakeLog("C1.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataC1.ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("C1.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataC1.ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S1.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataS1.ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S1.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataS1.ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());
}

private void Test3(int times, int count)
{
    var test = Enumerable.Range(1, times).ToArray();

    var dataC2 = (ICollection<PersonC>) new List<PersonC>(C(count));
    var dataS2 = (ICollection<PersonS>) new List<PersonS>(S(count));

    MakeLog("C2.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataC2.ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("C2.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataC2.ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S2.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataS2.ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S2.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataS2.ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());
}

private void TestMain()
{
    const int times = 100;
    const int count = 1_000_000 + 1;
    Test1(times, count);
    Test2(times, count);
    Test3(times, count);
}

J'ai obtenu ces résultats sur ma machine:

Groupe 1:

C.ToList 761.79 ms -> [775, 755, 759, 759, 756, 759, 765, 750, 757, 762, 759, 754, 757, 753, 763, 753, 759, 756, 768, 754, 763, 757, 757, 777, 780, 758, 754, 758, 762, 754, 758, 757, 763, 758, 760, 754, 761, 755, 764, 847, 952, 755, 747, 763, 760, 758, 754, 763, 761, 758, 750, 764, 757, 763, 762, 756, 753, 759, 759, 757, 758, 779, 765, 760, 760, 756, 760, 756, 755, 764, 759, 753, 757, 760, 752, 764, 758, 760, 758, 760, 755, 761, 751, 753, 761, 762, 761, 758, 759, 752, 765, 756, 760, 755, 757, 753, 760, 751, 755, 779]
C.ToArray 782.56 ms -> [783, 774, 771, 771, 773, 774, 775, 775, 772, 770, 771, 774, 771, 1023, 975, 772, 767, 776, 771, 779, 772, 779, 775, 771, 775, 773, 775, 771, 765, 774, 770, 781, 772, 771, 781, 762, 817, 770, 775, 779, 769, 774, 763, 775, 777, 769, 777, 772, 775, 778, 775, 771, 770, 774, 772, 769, 772, 769, 774, 775, 768, 775, 769, 774, 771, 776, 774, 773, 778, 769, 778, 767, 770, 787, 783, 779, 771, 768, 805, 780, 779, 767, 773, 771, 773, 785, 1044, 853, 775, 774, 775, 771, 770, 769, 770, 776, 770, 780, 821, 770]
S.ToList 704.2 ms -> [687, 702, 709, 691, 694, 710, 696, 698, 700, 694, 701, 719, 706, 694, 702, 699, 699, 703, 704, 701, 703, 705, 697, 707, 691, 697, 707, 692, 721, 698, 695, 700, 704, 700, 701, 710, 700, 705, 697, 711, 694, 700, 695, 698, 701, 692, 696, 702, 690, 699, 708, 700, 703, 714, 701, 697, 700, 699, 694, 701, 697, 696, 699, 694, 709, 1068, 690, 706, 699, 699, 695, 708, 695, 704, 704, 700, 695, 704, 695, 696, 702, 700, 710, 708, 693, 697, 702, 694, 700, 706, 699, 695, 706, 714, 704, 700, 695, 697, 707, 704]
S.ToArray 742.5 ms -> [742, 743, 733, 745, 741, 724, 738, 745, 728, 732, 740, 727, 739, 740, 726, 744, 758, 732, 744, 745, 730, 739, 738, 723, 745, 757, 729, 741, 736, 724, 744, 756, 739, 766, 737, 725, 741, 742, 736, 748, 742, 721, 746, 1043, 806, 747, 731, 727, 742, 742, 726, 738, 746, 727, 739, 743, 730, 744, 753, 741, 739, 746, 728, 740, 744, 734, 734, 738, 731, 747, 736, 731, 765, 735, 726, 740, 743, 730, 746, 742, 725, 731, 757, 734, 738, 741, 732, 747, 744, 721, 742, 741, 727, 745, 740, 730, 747, 760, 737, 740]

C1.ToList 32.34 ms -> [35, 31, 31, 31, 32, 31, 31, 31, 31, 31, 31, 31, 31, 31, 33, 32, 31, 31, 31, 31, 30, 32, 31, 31, 31, 31, 32, 30, 31, 31, 31, 30, 32, 31, 31, 31, 36, 31, 31, 31, 32, 30, 31, 32, 31, 31, 31, 31, 31, 32, 31, 31, 31, 31, 33, 32, 31, 32, 31, 31, 33, 31, 31, 31, 31, 31, 32, 31, 32, 31, 34, 38, 68, 42, 79, 33, 31, 31, 31, 31, 31, 30, 30, 30, 30, 31, 31, 31, 31, 32, 31, 32, 31, 31, 31, 32, 33, 33, 31, 31]
C1.ToArray 56.32 ms -> [57, 56, 59, 54, 54, 55, 56, 57, 54, 54, 55, 55, 57, 56, 59, 57, 56, 58, 56, 56, 54, 56, 57, 55, 55, 55, 57, 58, 57, 58, 55, 55, 56, 55, 57, 56, 56, 59, 56, 56, 56, 56, 58, 56, 57, 56, 56, 57, 56, 55, 56, 56, 56, 59, 56, 56, 56, 55, 55, 54, 55, 54, 57, 56, 56, 56, 55, 55, 56, 56, 56, 59, 56, 56, 57, 56, 57, 56, 56, 56, 56, 62, 55, 56, 56, 56, 69, 57, 58, 56, 57, 58, 56, 57, 56, 56, 56, 56, 56, 56]
S1.ToList 88.69 ms -> [96, 90, 90, 89, 91, 88, 89, 90, 96, 89, 89, 89, 90, 90, 90, 89, 90, 90, 89, 90, 89, 91, 89, 91, 89, 91, 89, 90, 90, 89, 87, 88, 87, 88, 87, 87, 87, 87, 88, 88, 87, 87, 89, 87, 87, 87, 91, 88, 87, 86, 89, 87, 90, 89, 89, 90, 89, 87, 87, 87, 86, 87, 88, 90, 88, 87, 87, 92, 87, 87, 88, 88, 88, 86, 86, 87, 88, 87, 87, 87, 89, 87, 89, 87, 90, 89, 89, 89, 91, 89, 90, 89, 90, 88, 90, 90, 90, 88, 89, 89]
S1.ToArray 143.26 ms -> [130, 129, 130, 131, 133, 130, 131, 130, 135, 137, 130, 136, 132, 131, 130, 131, 132, 130, 132, 136, 130, 131, 157, 153, 194, 364, 176, 189, 203, 194, 189, 192, 183, 140, 142, 147, 145, 134, 159, 158, 142, 167, 130, 143, 145, 144, 160, 154, 156, 153, 153, 164, 142, 145, 137, 134, 145, 143, 142, 135, 133, 133, 135, 134, 134, 139, 139, 133, 134, 141, 133, 132, 133, 132, 133, 131, 135, 132, 133, 132, 128, 128, 130, 132, 129, 129, 129, 129, 129, 128, 134, 129, 129, 129, 129, 128, 128, 137, 130, 131]

C2.ToList 3.25 ms -> [5, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 3, 3, 4, 4, 3, 3, 3, 4, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 4, 3, 3, 3, 3, 3, 4, 3, 4, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 3, 3, 4, 3, 3, 4, 3, 3, 3, 4, 3, 3, 3, 3, 3]
C2.ToArray 3.37 ms -> [4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 5, 4, 9, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 4, 4, 3, 3, 4, 4, 3, 3, 3, 4, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 4, 4, 3, 4, 4, 3, 3, 4, 3, 3, 4, 3, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 4, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 4, 3, 4, 3, 3, 3]
S2.ToList 37.72 ms -> [38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 40, 38, 38, 39, 39, 38, 38, 38, 38, 37, 37, 37, 37, 39, 37, 37, 39, 38, 37, 37, 37, 37, 39, 38, 37, 37, 38, 37, 38, 37, 37, 38, 37, 37, 37, 38, 37, 37, 36, 37, 38, 37, 39, 37, 39, 38, 37, 38, 38, 38, 38, 38, 38, 37, 38, 38, 38, 38, 38, 37, 38, 37, 37, 38, 37, 37, 39, 41, 37, 38, 38, 37, 37, 37, 37, 38, 37, 37, 37, 40, 37, 37, 37, 37, 39, 38]
S2.ToArray 38.86 ms -> [39, 37, 39, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 39, 38, 38, 38, 38, 38, 38, 38, 38, 39, 39, 38, 38, 38, 39, 37, 38, 38, 38, 38, 38, 37, 37, 38, 37, 37, 38, 38, 40, 38, 38, 38, 38, 38, 39, 38, 38, 39, 38, 38, 39, 38, 38, 40, 38, 39, 38, 38, 39, 38, 38, 38, 38, 38, 39, 38, 38, 38, 39, 39, 37, 38, 38, 39, 71, 78, 37, 37, 37, 39, 38, 38, 39, 38, 38, 38, 38, 38, 39, 38, 38, 38, 39, 38, 38, 38]

Groupe2:

C.ToList 756.81 ms
C.ToArray 774.21 ms
S.ToList 709.7 ms
S.ToArray 753.51 ms

C1.ToList 32.06 ms
C1.ToArray 56.58 ms
S1.ToList 89.43 ms
S1.ToArray 132.85 ms

C2.ToList 3.45 ms
C2.ToArray 3.36 ms
S2.ToList 41.43 ms
S2.ToArray 40.84 ms

Groupe3:

C.ToList 756.64 ms
C.ToArray 771.56 ms
S.ToList 705.42 ms
S.ToArray 749.59 ms

C1.ToList 31.45 ms
C1.ToArray 57.03 ms
S1.ToList 91.26 ms
S1.ToArray 129.77 ms

C2.ToList 3.26 ms
C2.ToArray 3.29 ms
S2.ToList 41.57 ms
S2.ToArray 40.69 ms

Groupe4:

C.ToList 729.65 ms -> [749, 730, 721, 719, 723, 743, 721, 724, 727, 722, 716, 725, 723, 726, 718, 722, 731, 722, 723, 725, 723, 722, 728, 726, 728, 718, 726, 1088, 788, 737, 729, 710, 730, 728, 717, 723, 728, 721, 722, 728, 722, 736, 723, 729, 732, 724, 726, 727, 728, 728, 726, 726, 725, 727, 725, 728, 728, 718, 724, 725, 726, 724, 726, 729, 727, 722, 722, 725, 725, 728, 724, 727, 738, 717, 726, 723, 725, 725, 727, 724, 720, 726, 726, 723, 727, 730, 723, 721, 725, 727, 727, 733, 720, 722, 722, 725, 722, 725, 728, 726]
C.ToArray 788.36 ms -> [748, 740, 742, 797, 1090, 774, 781, 787, 784, 786, 786, 782, 781, 781, 784, 783, 783, 781, 783, 787, 783, 784, 775, 789, 784, 785, 778, 774, 781, 783, 786, 781, 780, 788, 778, 785, 777, 781, 786, 782, 781, 787, 782, 787, 784, 773, 783, 782, 781, 777, 783, 781, 785, 788, 777, 776, 784, 784, 783, 789, 778, 781, 791, 768, 779, 783, 781, 787, 786, 781, 784, 781, 785, 781, 780, 809, 1155, 780, 790, 789, 783, 776, 785, 783, 786, 787, 782, 782, 787, 777, 779, 784, 783, 776, 786, 775, 782, 779, 784, 784]
S.ToList 705.54 ms -> [690, 705, 709, 708, 702, 707, 703, 696, 703, 702, 700, 703, 700, 707, 705, 699, 697, 703, 695, 698, 707, 697, 711, 710, 699, 700, 708, 707, 693, 710, 704, 691, 702, 700, 703, 700, 705, 700, 703, 695, 709, 705, 698, 699, 709, 700, 699, 704, 691, 705, 703, 700, 708, 1048, 710, 706, 706, 692, 702, 705, 695, 701, 710, 697, 698, 706, 705, 707, 707, 695, 698, 704, 698, 699, 705, 698, 703, 702, 701, 697, 702, 702, 704, 703, 699, 707, 703, 705, 701, 717, 698, 695, 713, 696, 708, 705, 697, 699, 700, 698]
S.ToArray 745.01 ms -> [751, 743, 727, 734, 736, 745, 739, 750, 739, 750, 758, 739, 744, 738, 730, 744, 745, 739, 744, 750, 733, 735, 743, 731, 749, 748, 727, 746, 749, 731, 737, 803, 1059, 756, 769, 748, 740, 745, 741, 746, 749, 732, 741, 742, 732, 744, 746, 737, 742, 739, 733, 744, 741, 729, 746, 760, 725, 741, 764, 739, 750, 751, 727, 745, 738, 727, 735, 741, 720, 736, 740, 733, 741, 746, 731, 749, 756, 740, 738, 736, 732, 741, 741, 733, 741, 744, 736, 742, 742, 735, 743, 746, 729, 748, 765, 743, 734, 742, 728, 749]

C1.ToList 32.27 ms -> [36, 31, 31, 32, 31, 32, 31, 30, 32, 30, 30, 30, 34, 32, 31, 31, 31, 31, 31, 31, 31, 32, 38, 51, 68, 57, 35, 30, 31, 31, 30, 30, 33, 30, 31, 34, 31, 34, 32, 31, 31, 31, 31, 32, 30, 30, 31, 30, 31, 31, 32, 31, 31, 31, 32, 31, 31, 31, 32, 31, 33, 31, 31, 32, 30, 30, 30, 30, 30, 33, 30, 33, 32, 31, 30, 31, 31, 32, 32, 31, 35, 31, 34, 31, 31, 32, 31, 31, 32, 31, 32, 31, 31, 35, 31, 31, 31, 31, 31, 32]
C1.ToArray 56.72 ms -> [58, 56, 57, 57, 59, 58, 58, 57, 56, 59, 57, 55, 55, 54, 56, 55, 56, 56, 57, 59, 56, 55, 58, 56, 55, 55, 55, 55, 58, 58, 55, 57, 57, 56, 57, 57, 57, 57, 59, 59, 56, 57, 56, 57, 57, 56, 57, 59, 58, 56, 57, 57, 57, 58, 56, 56, 59, 56, 59, 57, 57, 57, 57, 59, 57, 56, 57, 56, 58, 56, 57, 56, 57, 59, 55, 58, 55, 55, 56, 56, 56, 56, 56, 56, 56, 56, 56, 57, 56, 56, 57, 56, 56, 57, 58, 57, 57, 57, 57, 57]
S1.ToList 90.72 ms -> [95, 90, 90, 89, 89, 89, 91, 89, 89, 87, 91, 89, 89, 89, 91, 89, 89, 89, 90, 89, 89, 90, 88, 89, 88, 90, 89, 90, 89, 89, 90, 90, 89, 89, 90, 91, 89, 91, 89, 90, 89, 89, 90, 91, 89, 89, 89, 89, 89, 89, 90, 89, 89, 89, 90, 89, 90, 89, 91, 89, 90, 89, 90, 89, 90, 89, 96, 89, 90, 89, 89, 89, 89, 89, 90, 89, 89, 89, 90, 87, 89, 90, 90, 91, 89, 91, 89, 89, 90, 91, 90, 89, 93, 144, 149, 90, 90, 89, 89, 89]
S1.ToArray 131.4 ms -> [130, 128, 127, 134, 129, 129, 130, 136, 131, 130, 132, 132, 133, 131, 132, 131, 133, 132, 130, 131, 132, 131, 130, 133, 133, 130, 130, 131, 131, 131, 132, 134, 131, 131, 132, 131, 132, 131, 134, 131, 131, 130, 131, 131, 130, 132, 129, 131, 131, 131, 132, 131, 133, 134, 131, 131, 132, 132, 131, 133, 131, 131, 130, 133, 131, 130, 134, 132, 131, 132, 132, 131, 131, 134, 131, 131, 132, 132, 131, 130, 138, 130, 130, 131, 132, 132, 130, 134, 131, 131, 132, 131, 130, 132, 133, 131, 131, 131, 130, 131]

C2.ToList 3.21 ms -> [4, 3, 3, 3, 4, 3, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 4, 3, 4, 3, 3, 3, 3, 3, 4, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 4, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 3, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 4, 3, 3, 3]
C2.ToArray 3.22 ms -> [4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 4, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 3, 3, 4, 3, 4, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 4, 3, 4, 3, 3, 3, 3, 3, 4, 3, 3, 3, 4, 3, 4, 3, 3, 3, 3, 4]
S2.ToList 41.46 ms -> [42, 40, 41, 40, 42, 40, 40, 40, 40, 40, 40, 40, 40, 41, 40, 40, 41, 40, 40, 40, 39, 41, 41, 39, 40, 40, 43, 40, 39, 40, 40, 40, 40, 40, 40, 41, 40, 40, 40, 43, 40, 43, 75, 76, 47, 39, 40, 40, 40, 40, 42, 40, 41, 40, 40, 40, 44, 41, 40, 42, 42, 40, 41, 41, 41, 41, 41, 40, 41, 41, 41, 41, 42, 41, 40, 41, 41, 42, 42, 41, 40, 41, 41, 41, 41, 41, 40, 42, 40, 42, 41, 41, 41, 43, 41, 41, 41, 41, 42, 41]
S2.ToArray 41.14 ms -> [42, 41, 41, 40, 40, 40, 40, 41, 41, 42, 41, 42, 41, 41, 41, 42, 41, 41, 42, 41, 41, 41, 41, 41, 42, 40, 41, 40, 42, 40, 42, 41, 40, 42, 41, 41, 43, 42, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 40, 40, 41, 41, 41, 40, 42, 41, 41, 41, 41, 41, 40, 41, 41, 42, 41, 41, 41, 42, 41, 41, 41, 41, 41, 41, 42, 42, 42, 41, 45, 46, 41, 40, 41, 41, 42, 41, 41, 41, 41, 41, 41, 40, 41, 43, 40, 40, 40, 40, 43, 41]

Groupe5:

C.ToList 757.06 ms -> [770, 752, 752, 751, 778, 763, 761, 763, 747, 758, 748, 747, 754, 749, 752, 753, 756, 762, 750, 753, 756, 749, 755, 757, 755, 756, 755, 744, 753, 758, 747, 751, 759, 751, 761, 755, 746, 752, 752, 749, 746, 752, 753, 755, 752, 755, 754, 754, 966, 937, 749, 759, 748, 747, 754, 749, 755, 750, 746, 754, 757, 752, 753, 745, 758, 755, 761, 753, 751, 755, 755, 752, 746, 756, 755, 746, 742, 751, 751, 749, 752, 751, 756, 756, 755, 742, 749, 754, 749, 756, 753, 751, 754, 752, 751, 754, 753, 749, 755, 756]
C.ToArray 772.8 ms -> [766, 772, 755, 763, 758, 767, 763, 762, 761, 768, 769, 763, 770, 757, 765, 760, 766, 759, 764, 761, 760, 777, 1102, 881, 759, 765, 758, 762, 772, 761, 758, 757, 765, 769, 769, 761, 762, 762, 763, 760, 770, 764, 760, 768, 758, 766, 763, 770, 769, 761, 764, 761, 761, 767, 761, 762, 764, 757, 765, 766, 767, 771, 753, 762, 769, 768, 759, 764, 764, 760, 763, 763, 763, 763, 763, 767, 761, 771, 760, 765, 760, 758, 768, 770, 751, 771, 767, 771, 765, 763, 760, 765, 765, 769, 767, 767, 1193, 774, 767, 764]
S.ToList 704.73 ms -> [682, 708, 705, 699, 705, 704, 695, 703, 702, 699, 701, 708, 699, 702, 703, 701, 701, 699, 701, 707, 707, 700, 701, 705, 700, 697, 706, 702, 701, 706, 699, 692, 702, 697, 707, 704, 697, 698, 699, 699, 702, 703, 698, 697, 702, 703, 702, 704, 694, 697, 707, 695, 711, 710, 700, 693, 703, 699, 699, 706, 698, 701, 703, 704, 698, 706, 700, 704, 701, 699, 702, 705, 694, 698, 709, 736, 1053, 704, 694, 700, 698, 696, 701, 700, 700, 706, 706, 692, 698, 707, 703, 695, 703, 699, 694, 708, 695, 694, 706, 695]
S.ToArray 744.17 ms -> [746, 740, 725, 740, 739, 731, 746, 760, 735, 738, 740, 734, 744, 748, 737, 744, 745, 727, 736, 738, 728, 743, 745, 735, 748, 760, 739, 748, 762, 742, 741, 747, 733, 746, 758, 742, 742, 741, 724, 744, 747, 727, 740, 740, 729, 742, 757, 741, 740, 742, 726, 739, 746, 1133, 749, 737, 730, 740, 747, 733, 747, 752, 731, 747, 742, 730, 741, 749, 731, 749, 743, 730, 747, 742, 731, 737, 745, 734, 739, 735, 727, 743, 752, 731, 744, 742, 729, 740, 746, 731, 739, 746, 733, 745, 743, 733, 739, 742, 727, 737]

C1.ToList 31.71 ms -> [35, 32, 32, 30, 31, 33, 31, 32, 32, 31, 31, 32, 32, 33, 32, 31, 31, 32, 31, 32, 32, 32, 31, 32, 33, 32, 31, 31, 31, 32, 31, 34, 31, 31, 32, 33, 32, 32, 31, 32, 34, 32, 31, 32, 33, 31, 32, 32, 31, 32, 32, 32, 32, 32, 32, 31, 31, 32, 31, 33, 30, 31, 32, 30, 30, 33, 32, 32, 34, 31, 31, 31, 31, 32, 31, 31, 31, 31, 32, 31, 31, 33, 31, 32, 32, 32, 33, 32, 31, 31, 31, 31, 31, 32, 32, 33, 32, 31, 31, 32]
C1.ToArray 59.53 ms -> [63, 57, 58, 58, 57, 59, 59, 57, 60, 131, 127, 67, 58, 56, 59, 56, 57, 58, 58, 58, 57, 59, 60, 57, 57, 59, 60, 57, 57, 57, 58, 58, 58, 58, 57, 57, 61, 57, 58, 57, 57, 57, 57, 57, 58, 58, 58, 58, 57, 58, 59, 57, 58, 57, 57, 59, 58, 58, 59, 57, 59, 57, 56, 56, 59, 56, 56, 59, 57, 58, 58, 58, 57, 58, 59, 59, 58, 57, 58, 62, 65, 57, 57, 57, 58, 60, 59, 58, 59, 57, 58, 57, 58, 59, 58, 58, 58, 59, 60, 58]
S1.ToList 82.78 ms -> [87, 82, 83, 83, 82, 82, 83, 84, 82, 83, 84, 84, 84, 82, 82, 84, 82, 84, 83, 84, 82, 82, 82, 81, 83, 83, 83, 84, 84, 82, 82, 83, 83, 83, 82, 83, 85, 83, 82, 82, 84, 82, 82, 83, 83, 83, 82, 82, 82, 83, 82, 83, 82, 84, 82, 83, 82, 83, 82, 82, 82, 84, 82, 83, 82, 82, 86, 83, 83, 82, 83, 83, 83, 82, 84, 82, 83, 81, 82, 82, 82, 82, 83, 83, 83, 82, 83, 84, 83, 82, 83, 83, 83, 82, 83, 84, 82, 82, 83, 83]
S1.ToArray 122.3 ms -> [122, 119, 119, 120, 119, 120, 120, 121, 119, 119, 122, 120, 120, 120, 122, 120, 123, 120, 120, 120, 121, 123, 120, 120, 120, 121, 120, 121, 122, 120, 123, 119, 121, 118, 121, 120, 120, 120, 119, 124, 119, 121, 119, 120, 120, 120, 120, 120, 122, 121, 123, 230, 203, 123, 119, 119, 122, 119, 120, 120, 120, 122, 120, 121, 120, 121, 120, 121, 120, 121, 120, 120, 120, 121, 122, 121, 123, 119, 119, 119, 119, 121, 120, 120, 120, 122, 121, 122, 119, 120, 120, 121, 121, 120, 121, 120, 121, 118, 118, 118]

C2.ToList 3.43 ms -> [5, 3, 4, 4, 4, 3, 4, 4, 4, 4, 4, 3, 3, 3, 4, 3, 3, 3, 4, 3, 4, 3, 4, 3, 4, 4, 3, 3, 3, 3, 4, 3, 3, 3, 4, 3, 4, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 6, 4, 4, 3, 3, 4, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 4, 3, 4, 4, 4, 3, 4, 4, 3, 4, 4, 4, 4, 4, 4, 3, 3, 3, 4, 4, 3, 3, 3, 3]
C2.ToArray 3.48 ms -> [3, 3, 3, 3, 4, 4, 3, 4, 4, 4, 3, 4, 3, 3, 4, 3, 3, 4, 3, 4, 3, 3, 3, 4, 3, 3, 3, 4, 3, 3, 3, 3, 4, 3, 4, 3, 3, 4, 3, 4, 3, 3, 3, 3, 4, 3, 3, 3, 3, 4, 3, 4, 3, 3, 4, 3, 3, 4, 3, 3, 4, 4, 3, 3, 4, 3, 4, 4, 3, 4, 4, 4, 4, 4, 3, 3, 3, 4, 4, 3, 4, 4, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 4, 4, 4, 4, 3]
S2.ToList 41.47 ms -> [41, 41, 49, 67, 82, 41, 41, 40, 40, 40, 40, 40, 41, 40, 40, 40, 40, 40, 41, 40, 42, 42, 40, 40, 41, 41, 41, 40, 41, 40, 41, 40, 41, 40, 42, 41, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 41, 41, 41, 41, 41, 42, 41, 41, 41, 42, 40, 41, 40, 40, 40, 42, 40, 41, 42, 41, 42, 41, 42, 40, 41, 41, 41, 41, 41, 41, 41, 41, 40, 41, 40, 41, 41, 41, 40, 41, 41, 40, 40, 41, 41, 41, 41, 41, 43, 40, 40, 41, 42, 41]
S2.ToArray 40.62 ms -> [42, 41, 44, 40, 40, 40, 40, 41, 41, 40, 41, 41, 41, 40, 41, 41, 40, 41, 41, 40, 41, 40, 40, 41, 42, 41, 41, 41, 40, 40, 40, 40, 40, 41, 41, 42, 40, 41, 41, 41, 41, 41, 40, 42, 40, 40, 41, 41, 41, 40, 41, 40, 40, 40, 40, 40, 41, 40, 40, 41, 40, 40, 40, 40, 41, 40, 41, 41, 41, 40, 41, 41, 40, 41, 40, 41, 42, 40, 41, 41, 42, 41, 41, 40, 41, 40, 41, 40, 41, 41, 40, 40, 40, 41, 41, 40, 40, 40, 40, 40]

En raison de la limite de stackoverflow au nombre de caractères de la réponse, les exemples de listes de Group2 et Group3 sont omis.

Comme vous pouvez le voir, ce n'est vraiment pas important à utiliser ToListou ToArrydans la plupart des cas.

Lors du traitement des IEnumerable<T>objets calculés à l'exécution , si la charge apportée par le calcul est plus lourde que les opérations d'allocation de mémoire et de copie de ToListet ToArray, la disparité est insignifiante ( C.ToList vs C.ToArrayet S.ToList vs S.ToArray).

La différence ne peut être observée que sur des IEnumerable<T>objets uniquement non calculés lors de l'exécution ( C1.ToList vs C1.ToArrayet S1.ToList vs S1.ToArray). Mais la différence absolue (<60 ms) est toujours acceptable sur un million de petits objets IEnumerable<T>. En fait, la différence est décidée par la mise en œuvre Enumerator<T>de IEnumerable<T>. Donc, si votre programme est vraiment vraiment très sensible à cela, vous devez profiler, profiler, profiler ! Enfin, vous constaterez probablement que le goulot d'étranglement n'est pas sur ToListou ToArray, mais le détail des énumérateurs.

Et le résultat de C2.ToList vs C2.ToArrayet S2.ToList vs S2.ToArraymontre que, vous ne vraiment pas besoin de prendre soin ToListou ToArraysur la non-exécution calculée ICollection<T>objets.

Bien sûr, ce ne sont que des résultats sur ma machine, le temps réel consacré à ces opérations sur une machine différente ne sera pas le même, vous pouvez le découvrir sur votre machine en utilisant le code ci-dessus.

La seule raison pour laquelle vous devez faire un choix est que vous avez des besoins spécifiques sur List<T>ou T[], comme décrit par la réponse de @Jeppe Stig Nielsen .

qaqz111
la source
1

Pour toute personne intéressée à utiliser ce résultat dans un autre Linq-to-sql tel que

from q in context.MyTable
where myListOrArray.Contains(q.someID)
select q;

le SQL généré est le même, que vous ayez utilisé une liste ou un tableau pour myListOrArray. Maintenant, je sais que certains peuvent demander pourquoi même énumérer avant cette déclaration, mais il existe une différence entre le SQL généré à partir d'un IQueryable vs (List ou Array).

Gary
la source