Ist es besser, ToList () oder ToArray () in LINQ-Abfragen aufzurufen?


519

Ich stoße oft auf den Fall, dass ich eine Abfrage genau dort bewerten möchte, wo ich sie deklariere. Dies liegt normalerweise daran, dass ich mehrmals darüber iterieren muss und die Berechnung teuer ist. Zum Beispiel:

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

Das funktioniert gut. Aber wenn ich das Ergebnis nicht gehen zu ändern, dann könnte ich auch nennen ToArray()statt ToList().

Ich frage mich jedoch, ob dies ToArray()beim ersten Aufruf implementiert wird ToList()und daher weniger speichereffizient ist als nur das Aufrufen ToList().

Bin ich verrückt? Sollte ich einfach anrufen ToArray()- sicher und sicher in dem Wissen, dass der Speicher nicht zweimal zugewiesen wird?


10
Wenn Sie jemals herausfinden möchten, was in .NET hinter den Vorhängen passiert, empfehle ich .NET Reflector
David Hedlund

32
@ DavidHedlund Ich empfehle .net Quellcode .
Gqqnbig

1
Ich bin nicht der Meinung, dass stackoverflow.com/questions/6750447/c-toarray-performance ein Duplikat dieser Frage ist, obwohl es eine wichtige Beziehung gibt. Sowohl die Speichernutzung (diese Frage) als auch die Leistung (andere Frage) sind interessante und nicht triviale Überlegungen. Sie können separat beschrieben werden, aber beide sollten bei der Entscheidung, einander vorzuziehen, eine Rolle spielen. Ich kann keine der Antworten auf diese oder die andere Frage als umfassend empfehlen. Es gibt mehrere Antworten, die zusammengenommen eine ziemlich vollständige Diskussion darüber liefern, wie man eine über die andere wählt.
Steve

1
@ Gqqnbig - der nützlichste Kommentar aller Zeiten! Danke :-)
Mark Cooper

Antworten:


366

Es sei denn, Sie benötigen lediglich ein Array, um andere Einschränkungen zu erfüllen, die Sie verwenden sollten ToList. In den meisten Szenarien ToArraywird mehr Speicher zugewiesen als ToList.

Beide verwenden Arrays für die Speicherung, haben jedoch ToListeine flexiblere Einschränkung. Das Array muss mindestens so groß sein wie die Anzahl der Elemente in der Sammlung. Wenn das Array größer ist, ist das kein Problem. Das ToArrayArray muss jedoch genau auf die Anzahl der Elemente zugeschnitten sein.

Um diese Einschränkung zu erfüllen, muss ToArrayhäufig mehr zugewiesen werden als ToList. Sobald es ein Array hat, das groß genug ist, weist es ein Array zu, das genau die richtige Größe hat, und kopiert die Elemente zurück in dieses Array. Dies kann nur vermieden werden, wenn der Wachstumsalgorithmus für das Array zufällig mit der Anzahl der Elemente übereinstimmt, die gespeichert werden müssen (definitiv in der Minderheit).

BEARBEITEN

Ein paar Leute haben mich nach der Konsequenz gefragt, dass der zusätzliche ungenutzte Speicher im List<T>Wert enthalten ist.

Dies ist ein berechtigtes Anliegen. Wenn die erstellte Sammlung langlebig ist, nach ihrer ToArrayErstellung nie geändert wird und eine hohe Wahrscheinlichkeit besteht, im Gen2-Heap zu landen, ist es möglicherweise besser, die zusätzliche Zuweisung von vornherein zu übernehmen.

Im Allgemeinen finde ich dies jedoch der seltenere Fall. Es kommt viel häufiger vor, dass viele ToArrayAnrufe sofort an andere kurzlebige Speichernutzungen weitergeleitet werden. In diesem Fall ToListist dies nachweislich besser.

Der Schlüssel hier ist, ein Profil zu erstellen, ein Profil zu erstellen und dann ein weiteres Profil zu erstellen.


14
Wäre der zusätzliche Speicher, der für die Erstellung des Arrays bereitgestellt wird, nicht für die Speicherbereinigung geeignet, während der zusätzliche Aufwand für die Liste bestehen bleibt? Ich sage, halte es einfacher. Wenn Sie Elemente hinzufügen oder entfernen müssen, gibt es dafür ein Tool. Wenn Sie dies nicht tun, gibt es dafür ein anderes Tool. Verwenden Sie die, die Sinn macht. Wenn Sie später ein Problem mit Speicher und Leistung feststellen und dies ist , ändern Sie es.
Anthony Pegram

1
@AnthonyPegram Ja, das ist eine gültige Überlegung. Wenn der Wert im Langzeitspeicher verwendet wird, nicht geändert wird und möglicherweise in Gen 2 aufgenommen wird, ist es möglicherweise besser, die zusätzliche Zuweisung jetzt zu zahlen, als den Gen 2-Heap zu verschmutzen. IME, obwohl ich das selten sehe. Es kommt viel häufiger vor, dass ToArray sofort an eine andere kurzlebige LINQ-Abfrage übergeben wird.
JaredPar

2
@AnthonyPegram Ich habe meine Antwort aktualisiert, um diese Seite der Diskussion aufzunehmen
JaredPar

8
@JaredPar Ich verstehe nicht, wie ToArraymehr Speicher zugewiesen werden kann, wenn die genaue Speicherortgröße benötigt wird, wo ToList<>offensichtlich automatische Ersatzspeicherorte vorhanden sind. (Autoincrease)
Royi Namir

5
@RoyiNamir, da ToArray zuerst die Zuordnungen im ToList-Stil mit Overhead ausführt und dann eine zusätzliche Zuordnung mit exakter Größe durchführt.
Timbo

169

Der Leistungsunterschied ist unbedeutend, da er List<T>als dynamisch dimensioniertes Array implementiert ist. Wenn Sie entweder ToArray()(das eine interne Buffer<T>Klasse zum Erweitern des Arrays verwendet) oder ToList()(das den List<T>(IEnumerable<T>)Konstruktor aufruft ) aufrufen, müssen Sie sie in ein Array einfügen und das Array vergrößern, bis es allen passt.

Wenn Sie eine konkrete Bestätigung dieser Tatsache wünschen, lesen Sie die Implementierung der fraglichen Methoden in Reflector - Sie werden sehen, dass sie sich auf nahezu identischen Code beschränken.


2
Eine interessante Tatsache, auf die ich gestoßen bin, ist, dass Linq to SQL bei korrelierten Abfragen, die durch die Verwendung einer durch einen Gruppenverknüpfungspunkt in Ihrer Projektion definierten Gruppe verursacht wurden, eine weitere Unterabfrage hinzufügt, um die Anzahl für diese Gruppe abzurufen. Ich gehe davon aus, dass dies in diesen Fällen bedeutet, dass die Größe der Sammlung bekannt ist, bevor die Elemente abgerufen werden, und somit direkt ein Array mit exakter Größe erstellt werden kann, das bei der Materialisierung der Ergebnisse Verarbeitungs- und Speicherressourcen spart.
Jpierson

133
Wenn die Anzahl im Voraus bekannt ist, ist die Leistung identisch. Wenn die Anzahl jedoch nicht im Voraus bekannt ist, besteht der einzige Unterschied zwischen ToArray()und ToList()darin, dass erstere den Überschuss kürzen müssen, was das Kopieren des gesamten Arrays beinhaltet, während letztere den Überschuss nicht kürzen, sondern einen Durchschnitt von 25 verwenden % Mehr Speicherplatz. Dies hat nur Auswirkungen, wenn der Datentyp groß ist struct. Nur Denkanstöße.
Scott Rippey

9
@EldritchConundrum 25% stammen aus dieser Logik: Wenn die Anzahl der Elemente unbekannt ist, wird beim Aufrufen ToListoder ToArrayErstellen eines kleinen Puffers begonnen. Wenn dieser Puffer gefüllt ist, verdoppelt er die Kapazität des Puffers und fährt fort. Da die Kapazität immer verdoppelt wird, liegt der nicht verwendete Puffer immer zwischen 0% und 50%.
Scott Rippey

2
@ScottRippey Ich habe gerade die Quelle der neuen Liste aus der IEnumerable-Quelle nachgeschlagen und überprüft, ob es sich bei der IEnumerable um eine ICollection handelt. Wenn dies der Fall ist, weist sie zunächst ein Array mit der genauen Größe zu, die von der Count-Eigenschaft benötigt wird wäre der Fall, wenn ToList () definitiv schneller wäre. Eine vollständige Antwort könnte diese Tatsache beinhalten, obwohl ich nicht denke, dass dies der häufigste Fall ist.
AndyClaw

3
@AndyClaw Beide Listund Bufferprüfen ICollection, in welchem ​​Fall die Leistung identisch ist.
Scott Rippey

54

(Sieben Jahre später...)

Einige andere (gute) Antworten haben sich auf mikroskopische Leistungsunterschiede konzentriert, die auftreten werden.

Dieser Beitrag ist nur eine Ergänzung, um den semantischen Unterschied zu erwähnen, der zwischen dem IEnumerator<T>von einem Array ( T[]) erzeugten und dem von a zurückgegebenen besteht List<T>.

Am besten anhand eines Beispiels:

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);
}

Der obige Code wird ohne Ausnahme ausgeführt und erzeugt die Ausgabe:

1
2
3
4
5
6
7
8
900
10

Dies zeigt, dass das IEnumarator<int>von einem zurückgegebene int[]nicht nachverfolgt, ob das Array seit der Erstellung des Enumerators geändert wurde.

Beachten Sie, dass ich die lokale Variable sourceals deklariert habe IList<int>. Auf diese Weise stelle ich sicher, dass der C # -Compiler die foreachAnweisung nicht in etwas optimiert, das einer for (var idx = 0; idx < source.Length; idx++) { /* ... */ }Schleife entspricht. Dies könnte der C # -Compiler tun, wenn ich var source = ...;stattdessen verwende. In meiner aktuellen Version des .NET Frameworks ist der hier verwendete Enumerator ein nicht öffentlicher Referenztyp, System.SZArrayHelper+SZGenericArrayEnumerator`1[System.Int32]aber dies ist natürlich ein Implementierungsdetail.

Nun, wenn ich ändern .ToArray()in .ToList(), erhalte ich nur:

1
2
3
4
5

gefolgt von einem System.InvalidOperationExceptionSprengsatz:

Sammlung wurde geändert; Aufzählungsoperation wird möglicherweise nicht ausgeführt.

Der zugrunde liegende Enumerator ist in diesem Fall der öffentliche veränderbare Werttyp System.Collections.Generic.List`1+Enumerator[System.Int32]( IEnumerator<int>in diesem Fall in einem Feld, weil ich ihn verwende IList<int>).

Zusammenfassend verfolgt der von a erzeugteList<T>Enumerator, ob sich die Liste während der Aufzählung ändert, während der von einem erzeugte EnumeratorT[]dies nicht tut. Berücksichtigen Sie diesen Unterschied bei der Auswahl zwischen.ToList()und.ToArray().

Oft wird eine zusätzliche hinzugefügt .ToArray()oder .ToList()um eine Sammlung zu umgehen, die nachverfolgt, ob sie während der Lebensdauer eines Enumerators geändert wurde.

(Wenn jemand wissen will , wie die List<>verfolgt , ob Sammlung geändert wurde, gibt es einen privaten Bereich _versionin dieser Klasse , die jedes Mal geändert wird , die List<>aktualisiert wird.)


28

Ich stimme @mquander zu, dass der Leistungsunterschied unbedeutend sein sollte. Ich wollte es jedoch messen, um sicherzugehen, also habe ich es getan - und es ist unbedeutend.

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

Jedes Quellarray / jede Quellliste hatte 1000 Elemente. Sie sehen also, dass sowohl Zeit- als auch Speicherunterschiede vernachlässigbar sind.

Mein Fazit: Sie können auch ToList () verwenden , da a List<T>mehr Funktionen als ein Array bietet, es sei denn, ein paar Bytes Speicher sind für Sie wirklich wichtig.


1
Ich frage mich, ob dieses Ergebnis anders wäre, wenn Sie einen großen structanstelle eines primitiven Typs oder einer primitiven Klasse verwenden würden.
Scott Rippey

12
Liste <T> .ToList ???? Welcher Sinn ? Versuchen Sie besser, eine IEnumerable einzugeben, die keine ICollection-Schnittstelle implementiert.
Grigory

8
Ich wollte sicherstellen, dass ich nur die Zeit des ToListoder des ToArrayAnrufs und nicht die Aufzählung von irgendwelchen messe IEnumerable. List <T> .ToList () erstellt immer noch eine neue List <T> - es wird nicht einfach "zurückgegeben".
EMP

23
-1 Das Verhalten von ToArray()und ToList()unterscheidet sich zu stark, wenn sie mit einem ICollection<T>Parameter geliefert werden - Sie führen nur eine einzelne Zuordnung und einen einzelnen Kopiervorgang durch. Beides List<T>und Arrayimplementieren ICollection<T>, sodass Ihre Benchmarks überhaupt nicht gültig sind.
Mohammad Dehghan

1
Für alle Interessierten habe ich meinen eigenen Benchmark als separate Antwort veröffentlicht . Es wird verwendet .Select(i => i), um das ICollection<T>Implementierungsproblem zu vermeiden , und enthält eine Kontrollgruppe, um zu sehen, wie viel Zeit nur für die Iteration über die Quelle benötigt wird IEnumerable<>.
StriplingWarrior

19

ToList()wird normalerweise bevorzugt, wenn Sie es verwenden IEnumerable<T>(z. B. von ORM). Wenn die Länge der Sequenz zu Beginn nicht bekannt ist, wird ToArray()eine Sammlung mit dynamischer Länge wie List erstellt und anschließend in ein Array konvertiert, was zusätzliche Zeit in Anspruch nimmt.


26
Ich habe entschieden, dass die Lesbarkeit in diesem Fall die Leistung übertrifft. Ich verwende ToList jetzt nur, wenn ich weitere Elemente hinzufügen möchte. In allen anderen Fällen (in den meisten Fällen) verwende ich ToArray. Aber danke für die Eingabe!
Frank Krueger

5
In ILSpy suchen, Enumerable.ToArray()Anrufe new Buffer<TSource>(source).ToArray(). Wenn die Quelle im Pufferkonstruktor ICollection implementiert, ruft sie source.CopyTo (items, 0) auf, und dann gibt .ToArray () das Array der internen Elemente direkt zurück. In diesem Fall ist keine Konvertierung erforderlich, die zusätzliche Zeit in Anspruch nimmt. Wenn die Quelle ICollection nicht implementiert, führt das ToArray zu einer Array-Kopie, um die zusätzlichen nicht verwendeten Positionen vom Ende des Arrays zu entfernen, wie in Scott Rippeys Kommentar oben beschrieben.
BrandonAGr

19

Der Speicher wird immer zweimal zugewiesen - oder etwas in der Nähe davon. Da Sie die Größe eines Arrays nicht ändern können, verwenden beide Methoden einen Mechanismus, um die Daten in einer wachsenden Sammlung zu erfassen. (Nun, die Liste ist eine wachsende Sammlung für sich.)

Die Liste verwendet ein Array als internen Speicher und verdoppelt bei Bedarf die Kapazität. Dies bedeutet, dass durchschnittlich 2/3 der Elemente mindestens einmal neu zugewiesen wurden, die Hälfte der Elemente mindestens zweimal, die Hälfte mindestens dreimal und so weiter. Das bedeutet, dass jeder Artikel im Durchschnitt 1,3-mal neu zugewiesen wurde, was nicht sehr viel Aufwand bedeutet.

Denken Sie auch daran, dass beim Sammeln von Zeichenfolgen die Sammlung selbst nur die Verweise auf die Zeichenfolgen enthält und die Zeichenfolgen selbst nicht neu zugewiesen werden.


Dies mag unwissend sein, aber geht die von Ihnen skizzierte 2/3, 1/3, 1/6 Logik nicht davon aus, dass das Array der Liste an Ort und Stelle erweitert werden kann? Das heißt, am Ende des Arrays befindet sich freier Speicherplatz, sodass die vorhandene Zuordnung nicht verschoben werden muss.

@ JonofAllTrades: Nein, das Array wird nie erweitert, die Speicherverwaltung in .NET macht das einfach nicht. Wenn es an Ort und Stelle erweitert würde, wäre eine Neuzuweisung der Elemente nicht erforderlich.
Guffa

Ah, ich verstehe: Die Elemente, die nicht neu zugewiesen wurden, mussten dies nicht tun, da sie sich in der endgültigen Zuordnung befanden. Alle in früheren Zuordnungen zugewiesenen Elemente werden verschoben, aber aufgrund der logarithmischen Zunahme der Array-Länge ist dies ein berechenbarer Bruchteil. Danke fürs klarstellen!

19

Es ist 2020 draußen und jeder verwendet .NET Core 3.1, daher habe ich beschlossen, einige Benchmarks mit Benchmark.NET durchzuführen.

TL; DR: ToArray () ist leistungsmäßig besser und vermittelt die Absicht besser, wenn Sie nicht vorhaben, die Sammlung zu mutieren.


    [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>();
    }

Die Ergebnisse sind:


    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)

1
Wenn Sie nicht vorhaben, die Sammlung zu mutieren, kann die Absicht besser mit ToImmutableArray()(aus dem System.Collections.Immutable-Paket) gezeigt werden. 😉
Arturo Torres Sánchez

@ ArturoTorresSánchez stimmt, aber wenn die Sammlung nicht außerhalb einer Methode verfügbar gemacht wird, würde ich nur ein Array verwenden.
Tyrrrz

2
Danke dafür. Die gewählte Antwort ist ein bloßes Argument und setzt Ergebnisse voraus, die diesem Argument folgen. Um dies wissenschaftlich zu tun und als Bonus zu wissen, wie groß der Unterschied ist, gibt es nur einen wirklichen Weg, dies zu wissen.
Jonas

15

Bearbeiten : Der letzte Teil dieser Antwort ist ungültig. Der Rest sind jedoch immer noch nützliche Informationen, daher lasse ich sie.

Ich weiß, dass dies ein alter Beitrag ist, aber nachdem ich die gleiche Frage gestellt und einige Nachforschungen angestellt habe, habe ich etwas Interessantes gefunden, das es wert sein könnte, geteilt zu werden.

Erstens stimme ich @mquander und seiner Antwort zu. Er sagt zu Recht, dass die beiden in Bezug auf die Leistung identisch sind.

Ich habe jedoch Reflector verwendet, um einen Blick auf die Methoden im System.Linq.EnumerableErweiterungs-Namespace zu werfen , und ich habe eine sehr häufige Optimierung festgestellt.
Wann immer möglich, wird die IEnumerable<T>Quelle in die Methode umgewandelt IList<T>oder ICollection<T>optimiert. Schauen Sie sich zum Beispiel an ElementAt(int).

Interessanterweise hat Microsoft nur optimiert IList<T>, aber nicht IList. Microsoft bevorzugt anscheinend die Verwendung der IList<T>Benutzeroberfläche.

System.ArrayNur implementiert IList, daher wird es von keiner dieser Erweiterungsoptimierungen profitieren.
Daher behaupte ich, dass die beste Vorgehensweise darin besteht, die .ToList()Methode zu verwenden.
Wenn Sie eine der Erweiterungsmethoden verwenden oder die Liste an eine andere Methode übergeben, besteht die Möglichkeit, dass sie für eine optimiert wird IList<T>.


16
Ich habe einen Test gemacht und etwas Überraschendes herausgefunden. Ein Array implementiert IList <T>! Die Verwendung von Reflector zur Analyse von System.Array zeigt nur eine Vererbungskette von IList, ICollection, IEnumerable an. Bei Verwendung der Laufzeitreflexion stellte ich jedoch fest, dass string [] eine Vererbungskette von IList, ICollection, IEnumerable, IList <string>, ICollection <string aufweist >, IEnumerable <string>. Daher habe ich keine bessere Antwort als @mquander!
Scott Rippey

@ ScottRippey Ja. Die seltsame Beobachtung, die Sie bemerkt haben, ist tatsächlich Teil eines "Hacks" - und sie hat auch einige seltsame Auswirkungen auf die "feste Größe" und ähnliche Eigenschaften (mit einigen Inkonsistenzen, je nachdem, wie Sie sie wirken). Es gibt einige ziemlich große Kommentare, die dieses Thema im .net-Quellcode berühren. Es tut mir leid, dass ich nicht verlinkt habe, aber wenn ich mich richtig erinnere, ist es ziemlich leicht zu finden (innerhalb der Array-Klasse). (Und es gibt auch eine große SO-Frage, die die Inkonsistenzen
bespricht

@ ScottRippey nur zu Ihrer Information Ich habe diese Antwort gefunden, die mit Ihrem Kommentar zu tun hat: stackoverflow.com/a/4482567/2063755
David Klempfner

14

Ich fand, dass die anderen Benchmarks, die die Leute hier gemacht haben, fehlen, also hier ist mein Knaller. Lassen Sie mich wissen, wenn Sie etwas mit meiner Methodik falsch finden.

/* 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

Sie können das LINQPad-Skript hier herunterladen .

Ergebnisse: ToArray vs ToList Leistung

Wenn Sie den obigen Code anpassen, werden Sie Folgendes feststellen:

  1. Der Unterschied ist beim Umgang mit kleineren Arrays weniger signifikant . Mehr Iterationen, aber kleinere Arrays
  2. Der Unterschied ist weniger signifikant, wenn es um ints als um strings geht.
  3. Die Verwendung von großen structs anstelle von strings nimmt im Allgemeinen viel mehr Zeit in Anspruch, ändert jedoch das Verhältnis nicht wesentlich.

Dies stimmt mit den Schlussfolgerungen der am besten bewerteten Antworten überein:

  1. Es ist unwahrscheinlich, dass Sie einen Leistungsunterschied bemerken, es sei denn, Ihr Code erzeugt häufig viele große Datenlisten. (Beim Erstellen von 1000 Listen mit jeweils 100K-Zeichenfolgen gab es nur einen Unterschied von 200 ms.)
  2. ToList() Läuft durchweg schneller und ist eine bessere Wahl, wenn Sie nicht vorhaben, lange an den Ergebnissen festzuhalten.

Aktualisieren

@JonHanna wies darauf hin, dass es abhängig von der Implementierung Selecteiner ToList()oder ToArray()Implementierung möglich ist , die Größe der resultierenden Sammlung im Voraus vorherzusagen. Das Ersetzen .Select(i => i)des obigen Codes durch Code Where(i => true) führt derzeit zu sehr ähnlichen Ergebnissen , und dies ist wahrscheinlicher, unabhängig von der .NET-Implementierung.

Benchmark mit Where anstelle von Select


In .NET Core sollten beide Fälle hier besser sein als in netfx, da es erkennt, dass die Größe sein wird, 100000und dies verwendet, um beide zu optimieren, ToList()und ToArray()mit ToArray()etwas weniger Leichtigkeit, weil es nicht den Schrumpfvorgang benötigt, den es benötigen würde Ansonsten hat der eine Ort ToList()den Vorteil. Das Beispiel in der Frage würde immer noch verlieren, da die WhereMittel einer solchen Größenvorhersage nicht durchgeführt werden können.
Jon Hanna

@ JonHanna: Danke für das schnelle Feedback. Ich wusste nicht, dass .NET Core diese Optimierung vornimmt. Das ist cool. In meinem Code .Select(i => i)könnte durch ersetzt werden, um dies .Where(i => true)zu korrigieren.
StriplingWarrior

Ja, das würde die Optimierung stoppen, die sich auf corefx auswirkt. Es könnte interessant sein, sowohl eine Größe zu haben, die eine Zweierpotenz ist (was ToArray()einen Vorteil bringen sollte), als auch eine, die nicht wie oben ist, und die Ergebnisse zu vergleichen.
Jon Hanna

@ JonHanna: Interessanterweise ToArray() verliert immer noch im besten Fall. Mit Math.Pow(2, 15)Elementen ist es (ToList: 700 ms, ToArray: 900 ms). Durch Hinzufügen eines weiteren Elements wird es erhöht (ToList: 925, ToArray: 1350). Ich frage mich, ob ToArraydas Array noch kopiert wird, auch wenn es bereits die perfekte Größe hat. Sie dachten wahrscheinlich, dass es selten genug war, dass es die zusätzliche Bedingung nicht wert war.
StriplingWarrior

Es wurde nicht bei exakter Größenübereinstimmung kopiert, noch bevor wir damit begannen, es in corefx zu optimieren. Es ist also der Fall, wo es die meisten Pausen bekommt.
Jon Hanna

12

Sie sollten Ihre Entscheidung für ToListoder ToArraybasierend auf der idealen Designauswahl treffen . Wenn Sie eine Sammlung wünschen, auf die nur durch Index iteriert und zugegriffen werden kann, wählen Sie ToArray. Wenn Sie später ohne großen Aufwand zusätzliche Funktionen zum Hinzufügen und Entfernen aus der Sammlung benötigen, führen Sie eine aus ToList(nicht wirklich, dass Sie einem Array nicht hinzufügen können, aber das ist normalerweise nicht das richtige Werkzeug dafür).

Wenn es auf die Leistung ankommt, sollten Sie auch überlegen, was schneller zu bedienen wäre. Realistisch gesehen werden Sie nicht ToListoder ToArraymillionenfach anrufen , aber möglicherweise millionenfach an der erhaltenen Sammlung arbeiten. In dieser Hinsicht []ist besser, da List<>ist []mit einem gewissen Aufwand. In diesem Thread finden Sie einen Effizienzvergleich: Welcher ist effizienter: List <int> oder int []

In meinen eigenen Tests vor einiger Zeit hatte ich ToArrayschneller gefunden . Und ich bin mir nicht sicher, wie schief die Tests waren. Der Leistungsunterschied ist jedoch so gering, dass er sich nur bemerkbar macht, wenn Sie diese Abfragen millionenfach in einer Schleife ausführen.


2
Ja - Wenn der Compiler weiß, dass Sie über ein Array iterieren (anstatt über eine IEnumerable <>), kann er die Iteration erheblich optimieren.
RobSiklos

12

Eine sehr späte Antwort, aber ich denke, sie wird für Googler hilfreich sein.

Sie saugen beide, wenn sie mit linq erstellt haben. Beide implementieren denselben Code, um bei Bedarf die Größe des Puffers zu ändern . ToArrayVerwendet intern eine Klasse zum Konvertieren IEnumerable<>in ein Array, indem ein Array mit 4 Elementen zugewiesen wird. Wenn dies nicht ausreicht, verdoppelt sich die Größe, indem ein neues Array erstellt wird, das die Größe des aktuellen Arrays verdoppelt und das aktuelle Array darauf kopiert. Am Ende wird ein neues Array der Anzahl Ihrer Artikel zugewiesen. Wenn Ihre Abfrage 129 Elemente zurückgibt, führt ToArray 6 Zuweisungen und Speicherkopiervorgänge durch, um ein Array mit 256 Elementen zu erstellen, und anschließend ein weiteres Array mit 129 Elementen. Soviel zur Speichereffizienz.

ToList macht dasselbe, überspringt jedoch die letzte Zuordnung, da Sie in Zukunft Elemente hinzufügen können. List ist es egal, ob es aus einer Linq-Abfrage erstellt oder manuell erstellt wird.

für die Erstellung Liste ist besser mit Speicher, aber schlechter mit CPU, da Liste eine generische Lösung ist. Jede Aktion erfordert Bereichsprüfungen zusätzlich zu den internen Bereichsprüfungen des .net für Arrays.

Wenn Sie also Ihre Ergebnismenge zu oft durchlaufen, sind Arrays gut, da dies weniger Bereichsprüfungen als Listen bedeutet und Compiler Arrays im Allgemeinen für den sequentiellen Zugriff optimieren.

Die Initialisierungszuordnung der Liste kann besser sein, wenn Sie beim Erstellen Kapazitätsparameter angeben. In diesem Fall wird das Array nur einmal zugewiesen, vorausgesetzt, Sie kennen die Ergebnisgröße. ToListof linq gibt keine Überladung an, um sie bereitzustellen. Daher müssen wir unsere Erweiterungsmethode erstellen, die eine Liste mit der angegebenen Kapazität erstellt und dann verwendet List<>.AddRange.

Um diese Antwort zu beenden, muss ich folgende Sätze schreiben

  1. Am Ende können Sie entweder ein ToArray oder eine ToList verwenden. Die Leistung ist nicht so unterschiedlich (siehe Antwort von @EMP).
  2. Sie verwenden C #. Wenn Sie Leistung benötigen, machen Sie sich keine Sorgen über das Schreiben von Hochleistungscode, sondern über das Schreiben von Code mit schlechter Leistung.
  3. Zielen Sie immer auf x64 für Hochleistungscode. AFAIK, x64 JIT basiert auf dem C ++ - Compiler und macht einige lustige Dinge wie die Optimierung der Schwanzrekursion.
  4. Mit 4.5 können Sie auch die profilgesteuerte Optimierung und die Multi-Core-JIT genießen.
  5. Endlich können Sie das Muster async / await verwenden, um es schneller zu verarbeiten.

Sie saugen beide? Haben Sie eine alternative Idee, die keine redundante Speicherzuweisung erfordert?
Nawfal

Im Zusammenhang mit der Frage, ja, beide saugen, aber wegen redundanter Zuweisungen und sonst nichts. Um die redundante Zuordnung zu reduzieren, können verknüpfte Listen auf Kosten des Speichers und der Iterationsgeschwindigkeit verwendet werden. Letztendlich machen wir das, wir machen Kompromisse. Eine weitere Idee, wenn Sie eine Liste mit einer Kapazität von beispielsweise 200 erstellen und dann Elemente laden möchten. Dies verringert auch die Redundanz, aber Arrays sind immer schneller, sodass dies ein weiterer Kompromiss ist.
Erdogan Kurtur

Eine Liste von 200 erstellen ? Das könnte eine Größenänderung vermeiden, aber ich habe über redundanten Speicher gesprochen. Sie können es nicht ändern, da es keine Vorkenntnisse über die Größe gibt. Sie können die Kapazität bereits im Konstruktor von a angeben List<T>, aber wenn Sie dies nicht tun oder wenn Sie es nicht können, können Sie nichts dagegen tun.
Nawfal

2
Die einzigen redundanten Daten im Speicher sind der Inhalt des Arrays, bei dem es sich um eine Liste von Zeigern handelt (in diesem Fall). Eine Million 64-Bit-Zeiger benötigen bis zu 8 MB Speicher, was im Vergleich zu einer Million Objekten, auf die sie zeigen, nichts ist. 200 ist nur eine Zahl, und es besteht die Möglichkeit, die Anzahl der Anrufe zur Größenänderung maximal fünfmal zu reduzieren. und ja, wir können nicht anders. Wir haben keine besseren Möglichkeiten. Ich habe keine bessere Lösung, aber das bedeutet nicht, dass ich nicht sagen darf, wo das Problem liegt.
Erdogan Kurtur

1
hmm am Ende ist es, wo Sie die Linie ziehen. Ich mag die aktuelle Implementierung. Der Ton Ihrer Antwort ließ mich denken, dass es eher Kritik war als wo das Problem liegt :)
Nawfal

7

Dies ist eine alte Frage - aber zum Nutzen der Benutzer, die darauf stoßen, gibt es auch eine Alternative zum "Auswendiglernen" der Aufzählung -, die dazu führt, dass eine Linq-Anweisung mehrfach zwischengespeichert und gestoppt wird, was ToArray () ist. und ToList () werden häufig verwendet, obwohl die Auflistungsattribute der Liste oder des Arrays niemals verwendet werden.

Memoize ist in der Bibliothek RX / System.Interactive verfügbar und wird hier erklärt: Mehr LINQ mit System.Interactive

(Aus dem Blog von Bart De'Smet, der sehr zu empfehlen ist, wenn Sie viel mit Linq to Objects arbeiten.)


4

Eine Möglichkeit besteht darin, eine eigene Erweiterungsmethode hinzuzufügen, die schreibgeschützt zurückgibt ICollection<T> . Dies kann besser sein als die Verwendung ToListoder ToArraywenn Sie weder die Indizierungseigenschaften eines Arrays / einer Liste verwenden noch eine Liste hinzufügen / daraus entfernen möchten.

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();
    }
}

Unit Tests:

[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);
    }
}

Es ist anzumerken, dass der schreibgeschützte Erfassungsvertrag nur vorsieht, dass der Benutzer des Objekts es möglicherweise nicht ändert, der Eigentümer dies jedoch weiterhin tun kann, wenn er einen Verweis darauf behält, der eine veränderbare Schnittstelle bietet. Informationen zu Schnittstellen, die gewährleisten, dass sich die zugrunde liegende Struktur niemals ändert, finden Sie in unveränderlichen Sammlungen. Um festzustellen, warum unveränderliche oder schreibgeschützte oder einfache Lese- / Schreibsammlungen besser oder schlechter sind, benötigt man einen Referenzpunkt zum Vergleich. Es gibt keine endgültige Antwort (sonst müssten wir uns nicht entscheiden).
Der

@tne Hinweis Ich mache Tolist vor AsReadOnly, daher gibt es keine Verweise auf die zugrunde liegende veränderbare Variable.
Weston

Sie haben völlig Recht, und das war wahrscheinlich der beste Weg, um Dinge zu tun, bevor unveränderliche Sammlungen in die BCL kamen (ich sehe, dass die erste Beta einen Monat nach Ihrer Antwort herauskam).
Der

Es gibt unveränderliche Sammlungen für die Thread-Sicherheit, bei denen Threads davon ausgehen können, dass sie sich nicht ändern. In diesem Fall wird eine neue Version erstellt, anstatt gegen die Leser anzutreten und sie zu ändern, während sie sie verwenden. Auf diese Weise muss niemand jemals ein Schloss erwerben.
Doug65536

4

ToListAsync<T>() Ist bevorzugt.

In Entity Framework 6 beide Methoden aufrufen schließlich zur gleichen internen Methode, aber ToArrayAsync<T>()Anrufe list.ToArray()am Ende, die als umgesetzt wird

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

Hat ToArrayAsync<T>()also einige Gemeinkosten, wird dabei ToListAsync<T>()bevorzugt.


1
Das ist eigentlich die Antwort, nach der ich gesucht habe, wie EF das macht. Ich wäre gespannt, wie es in EF Core geht.
Shimmy Weitzhandler

3

Alte Frage, aber immer neue Fragesteller.

Nach Quelle System.Linq.Enumerable , ToListRückkehr nur ein new List(source), während die ToArrayVerwendung einer new Buffer<T>(source).ToArray()ein zurückzukehren T[].

Informationen zur Speicherzuweisung:

Weisen Sie beim Ausführen eines IEnumerable<T>einzigen Objekts ToArrayeinmal mehr Speicher zu als ToList. In den meisten Fällen müssen Sie sich jedoch nicht darum kümmern, da GC bei Bedarf die Speicherbereinigung durchführt.

Über die Laufzeit effizient:

Diejenigen, die diese Frage stellen, können den folgenden Code auf Ihrem eigenen Computer ausführen, und Sie erhalten Ihre Antwort.

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);
}

Ich habe diese Ergebnisse auf meiner Maschine:

Gruppe 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]

Gruppe 2:

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

Gruppe 3:

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

Gruppe 4:

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]

Gruppe 5:

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]

Aufgrund der Beschränkung des Stapelüberlaufs auf die Zeichenmenge der Antwort werden die Beispiellisten von Gruppe2 und Gruppe3 weggelassen.

Wie Sie sehen können, ist es wirklich nicht wichtig, ToListoder ToArryin den meisten Fällen zu verwenden.

IEnumerable<T>Wenn bei der Verarbeitung von zur Laufzeit berechneten Objekten die durch die Berechnung verursachte Last höher ist als die Speicherzuweisung und die Kopiervorgänge von ToListund ToArray, ist die Disparität unbedeutend ( C.ToList vs C.ToArrayund S.ToList vs S.ToArray).

Der Unterschied kann nur bei nicht zur Laufzeit berechneten IEnumerable<T>Objekten ( C1.ToList vs C1.ToArrayund S1.ToList vs S1.ToArray) beobachtet werden. Die absolute Differenz (<60 ms) ist jedoch bei einer Million kleiner Objekte immer noch akzeptabel IEnumerable<T>. In der Tat wird der Unterschied durch die Implementierung Enumerator<T>von entschieden IEnumerable<T>. Wenn Ihr Programm diesbezüglich wirklich sehr, sehr sensibel ist, müssen Sie ein Profil, ein Profil, ein Profil erstellen ! Endlich werden Sie wahrscheinlich feststellen, dass der Engpass nicht auf ToListoder liegt ToArray, sondern auf den Details der Enumeratoren.

Und das Ergebnis von C2.ToList vs C2.ToArrayund S2.ToList vs S2.ToArrayzeigt, dass Sie sich wirklich nicht darum kümmern müssen ToListoder ToArraynicht zur Laufzeit berechnet werdenICollection<T> Objekte.

Dies ist natürlich nur ein Ergebnis auf meinem Computer. Die tatsächliche Zeit, die diese Vorgänge auf verschiedenen Computern verbringen, ist nicht gleich. Sie können dies auf Ihrem Computer mithilfe des obigen Codes herausfinden.

Der einzige Grund, warum Sie eine Wahl treffen müssen, ist, dass Sie spezielle Anforderungen an List<T>oder haben T[], wie in der Antwort von @Jeppe Stig Nielsen beschrieben .


1

Für alle, die daran interessiert sind, dieses Ergebnis in einem anderen Linq-to-SQL wie z

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

Dann ist die generierte SQL dieselbe, unabhängig davon, ob Sie eine Liste oder ein Array für myListOrArray verwendet haben. Jetzt weiß ich, dass einige fragen mögen, warum sie vor dieser Anweisung überhaupt aufzählen, aber es gibt einen Unterschied zwischen dem SQL, das aus einem IQueryable generiert wurde, vs (List oder Array).

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.