Czy lepiej jest wywoływać ToList () lub ToArray () w zapytaniach LINQ?

518

Często spotykam się z przypadkiem, w którym chcę sprawdzić zapytanie dokładnie tam, gdzie je zadeklaruję. Dzieje się tak zwykle dlatego, że muszę wielokrotnie iterować i jest to kosztowne. Na przykład:

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

To działa dobrze. Ale jeśli nie zamierzam modyfikować wyniku, mogę równie dobrze zadzwonić ToArray()zamiast ToList().

Zastanawiam się jednak, czy ToArray()jest realizowane przez pierwsze wywołanie ToList()i dlatego jest mniej wydajne pod względem pamięci niż zwykłe wywołanie ToList().

Czy jestem szalony? Czy powinienem po prostu zadzwonić ToArray()- bezpiecznie, mając świadomość, że pamięć nie zostanie przydzielona dwukrotnie?

Frank Krueger
źródło
10
Jeśli kiedykolwiek chcesz się dowiedzieć, co dzieje się za zasłonami w .NET, naprawdę polecam .NET Reflector
David Hedlund
32
@DavidHedlund Polecam kod źródłowy .net .
Gqqnbig
1
Nie zgadzam się, że stackoverflow.com/questions/6750447/c-toarray-performance jest duplikatem tego pytania, mimo że istnieje ważny związek. Zarówno użycie pamięci (to pytanie), jak i wydajność (inne pytanie) oraz są interesującymi i nietrywialnymi względami. Można je opisać osobno, ale oba powinny uwzględniać decyzję o wyborze jednego nad drugim. Nie mogę polecić żadnej z odpowiedzi na to lub inne pytanie jako wyczerpującej. Istnieje kilka odpowiedzi, które razem wzięte zapewniają raczej kompletną dyskusję na temat tego, jak wybrać jedną z drugiej.
steve
1
@Gqqnbig - najbardziej użyteczny komentarz w historii! Dzięki :-)
Mark Cooper

Odpowiedzi:

365

Chyba że potrzebujesz tablicy, aby spełnić inne ograniczenia, których powinieneś użyć ToList. W większości scenariuszy ToArrayprzydzieli więcej pamięci niż ToList.

Oba używają tablic do przechowywania, ale ToListmają bardziej elastyczne ograniczenie. Wymaga tablicy co najmniej tak dużej, jak liczba elementów w kolekcji. Jeśli tablica jest większa, nie stanowi to problemu. ToArrayWymaga jednak, aby tablica była dokładnie dopasowana do liczby elementów.

Aby spełnić to ograniczenie, ToArrayczęsto dokonuje się jeszcze jeden przydział niż ToList. Kiedy ma już wystarczająco dużą tablicę, przydziela tablicę o dokładnie odpowiednim rozmiarze i kopiuje elementy z powrotem do tej tablicy. Można tego uniknąć tylko wtedy, gdy algorytm powiększania dla tablicy zbiega się z liczbą elementów, które muszą być przechowywane (zdecydowanie w mniejszości).

EDYTOWAĆ

Kilka osób zapytało mnie o konsekwencje posiadania dodatkowej nieużywanej pamięci w List<T>wartości.

To jest ważny problem. Jeśli utworzona kolekcja jest długowieczna, nigdy nie jest modyfikowana po utworzeniu i ma duże szanse na wylądowanie na stosie Gen2, lepiej być może skorzystasz z dodatkowej alokacji z ToArraygóry.

Ogólnie jednak uważam, że jest to rzadszy przypadek. Znacznie częściej zdarza się widzieć wiele ToArraypołączeń, które są natychmiast przekazywane do innych krótkotrwałych zastosowań pamięci, w którym ToListto przypadku jest wyraźnie lepsza.

Kluczem tutaj jest profilowanie, profilowanie, a następnie profilowanie jeszcze bardziej.

JaredPar
źródło
14
Z drugiej strony, czy dodatkowa pamięć przydzielona do pracy przy tworzeniu tablicy nie kwalifikowałaby się do wyrzucania elementów bezużytecznych, podczas gdy dodatkowe obciążenie dla Listy pozostałoby? Mówię, żeby było prostsze. Jeśli potrzebujesz dodać lub usunąć elementy, istnieje na to narzędzie. Jeśli nie, istnieje inne narzędzie do tego. Użyj tego, który ma sens. Jeśli później odkryjesz problem z pamięcią i wydajnością, i to jest to , zmień to.
Anthony Pegram
1
@AnthonyPegram tak, to jest ważna uwaga do rozważenia. Jeśli wartość jest używana do przechowywania długoterminowego, nie zostanie zmodyfikowana i potencjalnie przejdzie do Gen 2, wtedy lepiej będzie, jeśli zapłacisz teraz dodatkowy przydział niż zanieczyszczenie sterty Gen 2. IME chociaż rzadko to widzę. Znacznie częściej zdarza się, że ToArray jest natychmiast przekazywany do innego krótkotrwałego zapytania LINQ.
JaredPar
2
@AnthonyPegram zaktualizowałem moją odpowiedź, aby uwzględnić tę stronę dyskusji
JaredPar
8
@JaredPar Nie rozumiem, w jaki sposób ToArraymożna przydzielić więcej pamięci, jeśli potrzebuje dokładnej wielkości lokalizacji, gdzie ToList<>oczywiście ma to automatyczne rezerwowe lokalizacje. (automatyczne zwiększenie)
Royi Namir
5
@RoyiNamir, ponieważ ToArray najpierw dokonuje alokacji w stylu ToList z narzutem, a następnie dokonuje dodatkowej alokacji o dokładnym rozmiarze.
Timbo
169

Różnica wydajności będzie nieznaczna, ponieważ List<T>jest implementowana jako tablica o dynamicznym rozmiarze. Wywołanie albo ToArray()(która wykorzystuje Buffer<T>klasę wewnętrzną do powiększenia tablicy) albo ToList()(która wywołuje List<T>(IEnumerable<T>)konstruktora) zakończy się kwestią umieszczenia ich w tablicy i powiększenia tablicy, aż dopasuje je wszystkie.

Jeśli chcesz konkretnego potwierdzenia tego faktu, sprawdź implementację omawianych metod w Reflector - zobaczysz, że sprowadzają się one do prawie identycznego kodu.

mqp
źródło
2
Ciekawym faktem, z którym się zetknąłem, jest to, że w przypadku skorelowanych zapytań spowodowanych użyciem grupy zdefiniowanej przez dołączenie do grupy w projekcji Linq dodaje do SQL kolejne pod-zapytanie, aby pobrać liczbę dla tej grupy. Zakładam, że oznacza to, że w tych przypadkach rozmiar kolekcji będzie znany przed pobraniem elementów, dzięki czemu można bezpośrednio utworzyć tablicę o dokładnych rozmiarach, co pozwoliłoby zaoszczędzić na przetwarzaniu i zasobach pamięci podczas materializacji wyników.
jpierson
133
Jeśli liczba jest znana z góry, wydajność jest identyczna. Jeśli jednak liczba nie jest znana z góry, jedyną różnicą między ToArray()i ToList()jest to, że ta pierwsza musi przyciąć nadmiar, co wiąże się z skopiowaniem całej tablicy, podczas gdy druga nie przycina nadwyżki, ale używa średnio 25 % więcej pamięci. Będzie to miało wpływ tylko wtedy, gdy typ danych jest duży struct. Tylko jedzenie do namysłu.
Scott Rippey,
9
@EldritchConundrum 25% pochodzi z tej logiki: jeśli liczba elementów jest nieznana, wówczas wywołanie ToListlub ToArrayrozpocznie się od utworzenia małego bufora. Kiedy ten bufor jest wypełniony, podwaja pojemność bufora i kontynuuje. Ponieważ pojemność jest zawsze podwojona, nieużywany bufor będzie zawsze wynosił od 0% do 50%.
Scott Rippey,
2
@ ScottRippey Właśnie szukałem źródła nowej listy ze źródła IEnumerable i sprawdza, czy IEnumerable jest ICollection, a jeśli tak, to zaczyna się od przydzielenia jednej tablicy o dokładnym rozmiarze wymaganym z właściwości Count, więc to byłoby tak, gdyby ToList () byłby zdecydowanie szybszy. Pełna odpowiedź może obejmować ten fakt, chociaż nie sądzę, że jest to najczęstszy przypadek.
AndyClaw,
3
@AndyClaw Zarówno Listi Buffersprawdzi ICollection, w którym to przypadku wydajność będzie identyczna.
Scott Rippey,
54

(siedem lat później...)

Kilka innych (dobrych) odpowiedzi skoncentrowało się na mikroskopowych różnicach wydajności, które wystąpią.

Ten post jest tylko uzupełnieniem, aby wspomnieć o semantycznej różnicy, jaka istnieje między IEnumerator<T>produkowaną przez tablicę ( T[]) w porównaniu do zwracanej przez a List<T>.

Najlepiej ilustruje to przykład:

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

Powyższy kod będzie działał bez wyjątku i generuje dane wyjściowe:

1
2)
3)
4
5
6
7
8
900
10

To pokazuje, że IEnumarator<int>zwrócone przez a int[]nie śledzi, czy tablica została zmodyfikowana od czasu utworzenia modułu wyliczającego.

Zauważ, że zadeklarowałem zmienną lokalną sourcejako IList<int>. W ten sposób upewniam się, że kompilator C # nie optymalizuje foreachinstrukcji do czegoś, co jest równoważne for (var idx = 0; idx < source.Length; idx++) { /* ... */ }pętli. Jest to coś, co kompilator C # może zrobić, jeśli użyję var source = ...;zamiast tego. W mojej bieżącej wersji środowiska .NET używany tutaj moduł wyliczający jest niepublicznym typem referencyjnym, System.SZArrayHelper+SZGenericArrayEnumerator`1[System.Int32]ale oczywiście jest to szczegół implementacyjny.

Teraz, jeśli zmienię .ToArray()się .ToList(), mam tylko:

1
2)
3)
4
5

po czym następuje System.InvalidOperationExceptionwysadzenie w powietrze:

Kolekcja została zmodyfikowana; Operacja wyliczenia może nie zostać wykonana.

Podstawowym modułem wyliczającym w tym przypadku jest publiczny zmienny typ wartości System.Collections.Generic.List`1+Enumerator[System.Int32]( IEnumerator<int>w tym przypadku zawarty w polu, ponieważ używam IList<int>).

Podsumowując, moduł wyliczający utworzony przezList<T>śledzenie śledzi, czy lista zmienia się podczas wyliczania, podczas gdy moduł wyliczający wytworzony przezT[]nie. Więc rozważ tę różnicę, wybierając pomiędzy.ToList()i.ToArray().

Ludzie często dodają jeden dodatek .ToArray() lub w .ToList()celu obejścia kolekcji, która śledzi, czy została zmodyfikowana w czasie życia modułu wyliczającego.

(Jeśli ktoś chce wiedzieć, jakList<> śledzi czy zbiór został zmodyfikowany, nie jest prywatnym polu _versionw tej klasie, która zmienia się za każdym razem List<>jest aktualizowana.)

Jeppe Stig Nielsen
źródło
28

Zgadzam się z @mquander, że różnica w wydajności powinna być nieznaczna. Chciałem jednak to sprawdzić, więc zrobiłem to - i jest to nieznaczące.

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

Każda tablica / lista źródłowa zawierała 1000 elementów. Widać więc, że różnice w czasie i pamięci są znikome.

Mój wniosek: równie dobrze możesz użyć ToList () , ponieważ List<T>zapewnia więcej funkcji niż tablica, chyba że kilka bajtów pamięci naprawdę ma dla ciebie znaczenie.

EMP
źródło
1
Zastanawiam się, czy ten wynik byłby inny, gdybyś użył dużego structzamiast prymitywnego typu lub klasy.
Scott Rippey
12
Lista <T> .ToList ???? Jaki sens ? Lepiej spróbuj podać IEnumerable, który nie implementuje interfejsu ICollection.
Grigory
8
Chciałem się upewnić, że mierzę tylko czas połączenia ToListlub ToArraypołączenia, a nie ich numerację IEnumerable. List <T> .ToList () nadal tworzy nową Listę <T> - nie tylko „zwraca to”.
EMP
23
-1 Zachowania ToArray()i ToList()różnią się zbytnio, gdy są dostarczane z ICollection<T>parametrem - wykonują tylko jedną alokację i jedną operację kopiowania. Zarówno List<T>i Arrayimplementuj ICollection<T>, więc twoje testy porównawcze w ogóle nie są ważne.
Mohammad Dehghan,
1
Dla wszystkich zainteresowanych opublikowałem własny test porównawczy jako osobną odpowiedź . Używa go, .Select(i => i)aby uniknąć ICollection<T>problemu z implementacją, i zawiera grupę kontrolną, aby zobaczyć, ile czasu zajmuje iteracja źródła IEnumerable<>.
StriplingWarrior
19

ToList()jest zwykle preferowany, jeśli używasz go IEnumerable<T>(na przykład z ORM). Jeśli długość sekwencji nie jest znana na początku, ToArray()tworzy kolekcję o dynamicznej długości, taką jak List, a następnie konwertuje ją na tablicę, co zajmuje więcej czasu.

Vitaliy Ulantikov
źródło
26
Uznałem, że w tym przypadku czytelność ma wpływ na wydajność. Teraz używam ToList tylko wtedy, gdy spodziewam się kontynuować dodawanie elementów. We wszystkich innych przypadkach (większość przypadków) używam ToArray. Ale dzięki za wkład!
Frank Krueger,
5
Patrząc w ILSpy, Enumerable.ToArray()połączenia new Buffer<TSource>(source).ToArray(). W konstruktorze buforów, jeśli źródło implementuje ICollection, wówczas wywołuje source.CopyTo (items, 0), a następnie .ToArray () zwraca bezpośrednio wewnętrzną tablicę przedmiotów. W takim przypadku nie ma konwersji, która wymaga dodatkowego czasu. Jeśli źródło nie implementuje ICollection, ToArray spowoduje skopiowanie tablicy w celu przycięcia dodatkowych nieużywanych lokalizacji z końca tablicy, jak opisano w komentarzu Scotta Rippeya powyżej.
BrandonAGr
19

Pamięć zawsze będzie przydzielana dwukrotnie - lub coś w tym stylu. Ponieważ nie można zmienić rozmiaru tablicy, obie metody wykorzystują jakiś mechanizm do gromadzenia danych w rosnącej kolekcji. (Cóż, lista sama w sobie rośnie.)

Lista używa tablicy jako pamięci wewnętrznej i podwaja pojemność w razie potrzeby. Oznacza to, że średnio 2/3 pozycji zostało przeniesionych co najmniej raz, połowa z nich została przydzielona co najmniej dwa razy, połowa tych co najmniej trzy razy i tak dalej. Oznacza to, że każdy przedmiot został średnio przeniesiony 1,3 razy, co nie jest zbyt duże.

Pamiętaj również, że jeśli zbierasz ciągi, sama kolekcja zawiera tylko odniesienia do ciągów, same ciągi nie są ponownie przydzielane.

Guffa
źródło
Może to być pytanie ignoranckie, ale czy zarysowana przez ciebie logika 2/3, 1/3, 1/6 nie zakłada, że ​​tablicę Listy można rozszerzyć na miejscu? Oznacza to, że na końcu tablicy jest wolne miejsce, więc nie trzeba przenosić istniejącego przydziału?
@JonofAllTrades: Nie, tablica nigdy nie jest rozszerzana, zarządzanie pamięcią w .NET po prostu tego nie robi. Gdyby został przedłużony na miejscu, nie byłoby potrzeby realokacji przedmiotów.
Guffa
Ach, rozumiem: przedmioty, które nie zostały ponownie przydzielone, nie musiały tego robić, ponieważ były w ostatecznej alokacji. Wszystkie elementy przydzielone w poprzednich przydziałach są przenoszone, ale ze względu na logarytmiczne zwiększenie długości tablicy jest to ułamek obliczalny. Dzięki za wytłumaczenie!
19

Na zewnątrz jest rok 2020 i wszyscy używają .NET Core 3.1, więc postanowiłem uruchomić testy porównawcze z Benchmark.NET.

TL; DR: ToArray () jest bardziej wydajny pod względem wydajności i lepiej przekazuje zamiar, jeśli nie planujesz mutować kolekcji.


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

Wyniki są następujące:


    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
źródło
1
Jeśli nie planujesz mutować kolekcji, myślę, że lepiej można pokazać zamiar ToImmutableArray()(z pakietu System.Collections.Immutable) 😉
Arturo Torres Sánchez
@ ArturoTorresSánchez prawda, ale jeśli kolekcja nie zostanie ujawniona poza metodą, po prostu użyłbym tablicy.
Tyrrrz
2
Dzięki za to. Wybrana odpowiedź jest jedynie argumentem i zakłada wyniki następujące po tym argumencie. Aby zrobić to naukowo i jako bonus wiedzieć, jaka jest różnica, istnieje tylko jeden prawdziwy sposób, aby wiedzieć.
Jonas
15

Edycja : ostatnia część tej odpowiedzi jest nieprawidłowa. Jednak reszta to wciąż przydatna informacja, więc ją zostawię.

Wiem, że to stary post, ale po zadaniu tego samego pytania i przeprowadzeniu badań znalazłem coś interesującego, co może być warte podzielenia się.

Po pierwsze, zgadzam się z @mquander i jego odpowiedzią. Ma rację mówiąc, że pod względem wydajności oba są identyczne.

Jednak korzystam z Reflectora, aby przyjrzeć się metodom w System.Linq.Enumerableprzestrzeni nazw rozszerzeń i zauważyłem bardzo powszechną optymalizację.
O ile to możliwe, IEnumerable<T>źródło jest przesyłane do IList<T>lub w ICollection<T>celu optymalizacji metody. Na przykład spójrz na ElementAt(int).

Co ciekawe, Microsoft postanowił zoptymalizować tylko IList<T>, ale nie IList. Wygląda na to, że Microsoft woli używać IList<T>interfejsu.

System.Arraytylko implementuje IList, więc nie skorzysta z żadnej z tych optymalizacji rozszerzeń.
Dlatego uważam, że najlepszą praktyką jest zastosowanie tej .ToList()metody.
Jeśli użyjesz jednej z metod rozszerzenia lub przekażesz listę innej metodzie, istnieje szansa, że ​​zostanie ona zoptymalizowana pod kątem IList<T>.

Scott Rippey
źródło
16
Zrobiłem test i odkryłem coś zaskakującego. Tablica NIE implementuje IList <T>! Używając Reflectora do analizy System.Array ujawnia tylko łańcuch dziedziczenia IList, ICollection, IEnumerable, ale używając refleksji w czasie wykonywania dowiedziałem się, że string [] ma łańcuch dziedziczenia IList, ICollection, IEnumerable, IList <ciąg> ICollection <ciąg >, IEnumerable <ciąg>. Dlatego nie mam lepszej odpowiedzi niż @mquander!
Scott Rippey,
@ScottRippey Tak. Dziwne spostrzeżenie, które zauważyłeś, jest w rzeczywistości częścią „włamania” - i ma również dość dziwne implikacje w odniesieniu do „stałego rozmiaru” i podobnych właściwości (z pewnymi niespójnościami w zależności od tego, jak rzuciłeś). Istnieją pewne dość duże komentarze dotyczące tego tematu w kodzie źródłowym .net. Przepraszam za brak linkowania, ale jeśli dobrze pamiętam, dość łatwo je znaleźć (wewnątrz klasy tablicowej). (I jest też duże pytanie SO omawiające niespójności ... gdzieś ...> __>)
AnorZaken
@ ScottRippey po prostu FYI Znalazłem odpowiedź, która ma związek z twoim komentarzem: stackoverflow.com/a/4482567/2063755
David Klempfner
14

Stwierdziłem, że brakuje innych testów porównawczych, których ludzie tutaj nie zrobili, więc oto mój problem. Daj mi znać, jeśli znajdziesz coś złego w mojej metodologii.

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

Możesz pobrać skrypt LINQPad tutaj .

Wyniki: Wydajność ToArray vs. ToList

Poprawiając powyższy kod, odkryjesz, że:

  1. Różnica jest mniej znacząca w przypadku mniejszych tablic . Więcej iteracji, ale mniejsze tablice
  2. Różnica jest mniej znacząca w przypadku ints zamiast strings.
  3. Używanie dużych structs zamiast strings zajmuje ogólnie dużo więcej czasu, ale tak naprawdę nie zmienia zbytnio współczynnika.

Jest to zgodne z wnioskami z najczęściej głosowanych odpowiedzi:

  1. Raczej nie zauważysz różnicy w wydajności, chyba że Twój kod często generuje wiele dużych list danych. (Podczas tworzenia 1000 list po 100 000 ciągów znaków istniała tylko 200 ms różnica).
  2. ToList() konsekwentnie działa szybciej i byłby lepszym wyborem, jeśli nie planujesz długo utrzymywać wyników.

Aktualizacja

@JonHanna zwrócił uwagę, że w zależności od implementacji Selectmożliwe jest, aby a ToList()lub ToArray()implementacja przewidziała rozmiar wynikowej kolekcji z góry. Zastąpienie .Select(i => i)powyższego kodu Where(i => true) wynikami jest obecnie bardzo podobne i jest bardziej prawdopodobne, niezależnie od implementacji platformy .NET.

Benchmark za pomocą Where zamiast Select

StriplingWarrior
źródło
W .NET Rdzeń obu przypadkach powinno być lepiej tutaj niż na netfx, ponieważ będzie to zrealizować rozmiar będzie 100000i użyć jej w celu optymalizacji zarówno ToList()i ToArray(), ze ToArray()jest bardzo nieznacznie lżejszy, ponieważ nie potrzebuje operacji skurczowej musiałaby w przeciwnym razie, które jest jednym miejscem ToList()ma tę zaletę. Przykład w pytaniu wciąż by przegrał, ponieważ Wherenie można wykonać takiego przewidywania wielkości.
Jon Hanna,
@JonHanna: Dzięki za szybką informację zwrotną. Nie wiedziałem, że .NET Core dokonuje takiej optymalizacji. To super. W moim kodzie .Select(i => i)można go zastąpić, .Where(i => true)aby to poprawić.
StriplingWarrior
Tak, to powstrzymałoby optymalizację wpływającą na to w Corefx. Interesujące może być posiadanie zarówno rozmiaru, który jest potęgą dwóch (co powinno dać ToArray()przewagę), jak i takiego, który nie jest, jak wyżej, i porównuje wyniki.
Jon Hanna,
@JonHanna: Co ciekawe, ToArray() wciąż przegrywa w najlepszym przypadku. Z Math.Pow(2, 15)elementami jest to (ToList: 700ms, ToArray: 900ms). Dodanie jeszcze jednego elementu powoduje jego zderzenie z (ToList: 925, ToArray: 1350). Zastanawiam się, czy ToArraynadal kopiuje tablicę, nawet jeśli ma już idealny rozmiar? Prawdopodobnie doszli do wniosku, że było to dość rzadkie zjawisko, które nie było warte dodatkowych warunków.
StriplingWarrior
Nie udało się skopiować dokładnego dopasowania rozmiaru, nawet zanim zaczęliśmy optymalizować go w corefx, więc jest to przypadek, w którym robi się najwięcej przerw.
Jon Hanna,
12

Powinieneś oprzeć swoją decyzję na wyborze ToListlub ToArrayna tym, co najlepiej wybrać projekt. Jeśli chcesz kolekcji, która może być iterowana i dostępna tylko za pomocą indeksu, wybierz ToArray. Jeśli chcesz dodatkowych możliwości dodawania i usuwania z kolekcji później bez większych problemów, zrób to ToList(nie tak naprawdę, że nie możesz dodać do tablicy, ale zwykle nie jest to odpowiednie narzędzie).

Jeśli wydajność ma znaczenie, powinieneś również rozważyć, na czym można by działać szybciej. Realistycznie, przyzwyczajenie zadzwonić ToListlub ToArraymilion razy, ale może pracować na otrzymanej kolekcji milion razy. Pod tym względem []jest lepiej, ponieważ List<>wiąże []się to z pewnym obciążeniem. Zobacz ten wątek dla porównania wydajności: Który jest bardziej wydajny: List <int> lub int []

W moich własnych testach jakiś czas temu znalazłem ToArrayszybciej. I nie jestem pewien, jak wypaczone były testy. Różnica w wydajności jest jednak tak nieznaczna, że ​​można to zauważyć tylko wtedy, gdy te zapytania są uruchamiane miliony razy w pętli.

nawfal
źródło
2
Tak - jeśli kompilator wie, że wykonujesz iterację na tablicy (zamiast IEnumerable <>), może znacznie zoptymalizować iterację.
RobSiklos,
12

Bardzo późna odpowiedź, ale myślę, że będzie to pomocne dla pracowników Google.

Oboje ssają, kiedy stworzyli za pomocą linq. Obie implementują ten sam kod, aby w razie potrzeby zmienić rozmiar bufora . ToArraywewnętrznie wykorzystuje klasę do konwersji IEnumerable<>na tablicę, przydzielając tablicę 4 elementów. Jeśli to nie wystarczy, podwaja rozmiar, tworząc nową tablicę dwukrotnie powiększając prąd i kopiując do niej tablicę bieżącą. Na koniec przydziela nowy zestaw liczników twoich przedmiotów. Jeśli zapytanie zwróci 129 elementów, ToArray dokona 6 operacji alokacji i operacji kopiowania pamięci, aby utworzyć tablicę 256-elementową, a następnie zostanie zwrócona inna tablica 129. tyle dla wydajności pamięci.

ToList robi to samo, ale pomija ostatni przydział, ponieważ możesz dodawać elementy w przyszłości. Lista nie ma znaczenia, czy jest tworzona z zapytania linq czy ręcznie.

do tworzenia Lista jest lepsza z pamięcią, ale gorsza z procesorem, ponieważ lista jest rozwiązaniem ogólnym, każda akcja wymaga dodatkowej kontroli zasięgu poza wewnętrznymi kontrolami zasięgu .net dla tablic.

Jeśli więc zbyt wiele razy będziesz iterował zestaw wyników, to tablice są dobre, ponieważ oznacza to mniej kontroli zasięgu niż listy, a kompilatory ogólnie optymalizują tablice pod kątem dostępu sekwencyjnego.

Przydział inicjalizacji listy może być lepszy, jeśli określisz parametr zdolności podczas jego tworzenia. W takim przypadku tablica przydzieli tablicę tylko raz, zakładając, że znasz rozmiar wyniku. ToListlinq nie określa przeciążenia, aby je zapewnić, dlatego musimy stworzyć naszą metodę rozszerzenia, która tworzy listę o określonej pojemności, a następnie używa List<>.AddRange.

Aby zakończyć tę odpowiedź, muszę napisać następujące zdania

  1. Na koniec możesz użyć ToArray lub ToList, wydajność nie będzie się tak różnić (patrz odpowiedź @EMP).
  2. Używasz C #. Jeśli potrzebujesz wydajności, nie martw się pisaniem o kodzie o wysokiej wydajności, ale martw się o to, że nie napiszesz złego kodu wydajności.
  3. Zawsze kieruj x64 na kod o wysokiej wydajności. AFAIK, x64 JIT jest oparty na kompilatorze C ++ i wykonuje kilka zabawnych rzeczy, takich jak optymalizacja rekurencji ogona.
  4. W wersji 4.5 możesz także korzystać z optymalizacji profilu i wielordzeniowego JIT.
  5. Nareszcie możesz użyć wzorca asynchronicznego / oczekującego, aby przetworzyć go szybciej.
Erdogan Kurtur
źródło
Oboje są do dupy? Czy masz alternatywny pomysł, który nie wymaga nadmiarowej alokacji pamięci?
nawfal
W kontekście pytania, tak, oboje są do bani, ale z powodu zbędnych przydziałów i nic więcej. Aby zredukować nadmiarowy przydział, można użyć połączonych list kosztem pamięci i szybkości iteracji. Pod koniec dnia to właśnie robimy, dokonujemy kompromisów. Innym pomysłem jest utworzenie listy o pojemności 200 (na przykład), a następnie załadowanie elementów. Zmniejszy to również redundancję, ale tablice są zawsze szybsze, więc jest to kolejna kompromis.
Erdogan Kurtur
Stwórz listę 200 ? To może uniknąć zmiany rozmiaru, ale mówiłem o używanej nadmiarowej pamięci. Nic nie możesz na to poradzić, ponieważ nie ma wcześniejszej wiedzy o tym, jaki mógłby być rozmiar. Możesz już określić pojemność w konstruktorze a List<T>, ale kiedy tego nie robisz lub kiedy nie możesz, nie możesz nic na to poradzić.
nawfal
2
jedyne nadmiarowe dane w pamięci to zawartość tablicy, która jest listą wskaźników (w tym przypadku). milion 64-bitowych wskaźników zajmuje aż 8 MB pamięci, co jest niczym w porównaniu z milionem wskazywanych obiektów. 200 to tylko liczba i ma szansę zmniejszyć liczbę połączeń o rozmiar maksymalnie maksymalnie 5 razy. i tak, nie możemy nic na to poradzić. nie mamy lepszych opcji. Nie mam lepszego rozwiązania, ale to nie znaczy, że nie wolno mi powiedzieć, gdzie jest problem.
Erdogan Kurtur
1
hmm na końcu to tam narysujesz linię. Podoba mi się obecne wdrożenie. Ton twojej odpowiedzi sprawił, że pomyślałem, że to była krytyka, a nie problem. :)
nawfal
7

To stare pytanie - ale z korzyścią dla użytkowników, którzy się na niego natkną, istnieje również alternatywa „Memoizing” the Enumerable - która powoduje buforowanie i zatrzymanie wielokrotnego wyliczania instrukcji Linq, co jest tym, co ToArray () i ToList () są używane często, mimo że atrybuty kolekcji listy lub tablicy nigdy nie są używane.

Zapamiętywanie jest dostępne w bibliotece RX / System.Interactive i jest wyjaśnione tutaj: Więcej LINQ z System.Interactive

(Z bloga Barta De'Smeta, który jest wysoce zalecany, jeśli często pracujesz z Linq do Objects)

Frep D-Oronge
źródło
4

Jedną z opcji jest dodanie własnej metody rozszerzenia, która zwraca tylko do odczytuICollection<T> . Może to być lepsze niż używanie ToListlub ToArraygdy nie chcesz używać właściwości indeksowania tablicy / listy lub dodawać / usuwać z listy.

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

Testy jednostkowe:

[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
źródło
Warto zauważyć, że umowa kolekcji tylko do odczytu stanowi, że użytkownik obiektu może go nie modyfikować, ale właściciel może nadal to robić, jeśli zachowuje odniesienie do niego, które oferuje zmienny interfejs. W przypadku interfejsów gwarantujących, że podstawowa struktura nigdy się nie zmieni, spójrz na niezmienne kolekcje. Co do tego, dlaczego niezmienne, tylko do odczytu lub zwykłe kolekcje do odczytu i zapisu są lepsze lub gorsze, potrzebny jest punkt odniesienia do porównania; nie ma ostatecznej odpowiedzi (inaczej nie musielibyśmy wybierać).
tne
@tne Uwaga Robię Tolist przed AsReadOnly, więc nie ma żadnych odniesień do bazowej zmiennej.
weston
Masz całkowitą rację i był to prawdopodobnie najlepszy sposób na zrobienie rzeczy, zanim niezmienne kolekcje pojawiły się w BCL (widzę, że pierwsza beta ukazała się miesiąc po twojej odpowiedzi).
tne
Istnieją niezmienne kolekcje dla bezpieczeństwa wątków, w których wątki mogą zakładać, że się nie zmienią, a jeśli tak, powstaje nowa wersja, zamiast ścigać się z czytelnikami i zmieniać ją podczas ich używania. W ten sposób nikt nigdy nie musi uzyskiwać blokady.
doug65536,
4

ToListAsync<T>() jest preferowany.

W Entity Framework 6 obie metody ostatecznie wywołują tę samą metodę wewnętrzną, ale na końcu ToArrayAsync<T>()wywołują list.ToArray(), która jest implementowana jako

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

ToArrayAsync<T>()Ma więc pewne koszty ogólne, dlatego ToListAsync<T>()jest preferowane.

Stephen Zeng
źródło
1
Właśnie takiej odpowiedzi szukałem, jak EF to robi. Byłbym ciekawy, jak to jest w EF Core.
Shimmy Weitzhandler
3

Stare pytanie, ale przez cały czas nowi pytający.

Według źródła System.Linq.Enumerable , ToListpo prostu zwróć a new List(source), a ToArrayużyj a, new Buffer<T>(source).ToArray()aby zwrócić a T[].

Informacje o przydziale pamięci:

Podczas jazdy na IEnumerable<T>jedynym obiektem, ToArraynależy przydzielić pamięci jeden więcej czasu niż ToList. Ale w większości przypadków nie musisz się tym przejmować, ponieważ GC zajmie się usuwaniem śmieci, gdy zajdzie taka potrzeba.

Informacje o wydajnym środowisku wykonawczym:

Ci, którzy kwestionują to pytanie, mogą uruchomić następujący kod na własnym komputerze, a otrzymasz odpowiedź.

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

Mam te wyniki na moim komputerze:

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

Grupa 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

Grupa 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

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

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

Z powodu ograniczenia stosu do liczby znaków w odpowiedzi, listy przykładowe z Grupy 2 i Grupy 3 zostały pominięte.

Jak widać, w rzeczywistości nie jest ważne używanie ToListlub ToArryw większości przypadków.

Podczas przetwarzania runtime-obliczony IEnumerable<T>obiektów, jeśli obciążenie wniesiona przez obliczeń jest ciężki niż alokacji pamięci i skopiować operacje ToListi ToArray, rozbieżność jest niewielka ( C.ToList vs C.ToArrayaS.ToList vs S.ToArray ).

Różnicę można zaobserwować tylko w przypadku IEnumerable<T>obiektów obliczanych tylko w środowisku wykonawczym ( C1.ToList vs C1.ToArrayi S1.ToList vs S1.ToArray). Ale różnica bezwzględna (<60 ms) jest wciąż akceptowalna dla miliona małych obiektów IEnumerable<T>. W rzeczywistości różnica decyduje realizacji Enumerator<T>od IEnumerable<T>. Tak więc, jeśli twój program jest naprawdę bardzo wrażliwy na ten temat, musisz profilować, profilować, profilować ! W końcu prawdopodobnie zauważysz, że wąskie gardło nie jest włączone ToListlubToArray , ale szczegół rachmistrzów.

A wynik C2.ToList vs C2.ToArrayi S2.ToList vs S2.ToArraypokazuje, że tak naprawdę nie trzeba się tym przejmować ToListani obiektami ToArrayobliczanymi poza środowiskiem uruchomieniowym ICollection<T>.

Oczywiście, to tylko wyniki na moim komputerze, faktyczny czas spędzony na tych operacjach na innym komputerze nie będzie taki sam, możesz dowiedzieć się na swoim komputerze za pomocą powyższego kodu.

Jedynym powodem, dla którego musisz dokonać wyboru, jest to, że masz określone potrzeby List<T>lub T[], jak opisano w odpowiedzi @Jeppe Stig Nielsen .

qaqz111
źródło
1

Dla wszystkich zainteresowanych wykorzystaniem tego wyniku w innym Linq-sql, takim jak

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

wówczas generowany kod SQL jest taki sam, niezależnie od tego, czy użyto listy, czy tablicy dla myListOrArray. Teraz wiem, że niektórzy mogą zapytać, dlaczego nawet wyliczyć przed tą instrukcją, ale istnieje różnica między SQL wygenerowanym z IQueryable vs (List lub Array).

Gary
źródło