EDIT 2: (this is a correction for the original answer)
Using Benchmark.NET we could confirm with performance measurements, that the accepted answer is actually correct: ToList is faster in the general case, because it doesn't have to trim the empty space from the allocated buffer. ToArray might do an additional allocation and copy operation such that the buffer will be sized exactly to the number of elements.
In order to confirm this the following benchmark is used.
[MemoryDiagnoser]
[ShortRunJob]
public class Benchmarks
{
[Params(0, 1, 6, 10, 42, 100, 1337, 10000)]
public int Count { get; set; }
public IEnumerable<int> Items => Enumerable.Range(0, Count).Where(i => i > 0);
[Benchmark(Baseline = true)]
public int[] ToArray() => Items.ToArray();
[Benchmark]
public List<int> ToList() => Items.ToList();
}
The results confirm that ToList is in most cases 10% - 15% faster.
BenchmarkDotNet=v0.13.1, OS=Windows 10.0.22000
Intel Core i9-10885H CPU 2.40GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK=6.0.302
[Host] : .NET 6.0.7 (6.0.722.32202), X64 RyuJIT
DefaultJob : .NET 6.0.7 (6.0.722.32202), X64 RyuJIT
| Method | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Allocated |
|-------- |------ |-------------:|-----------:|-----------:|------:|--------:|--------:|-------:|----------:|
| ToArray | 0 | 29.73 ns | 0.546 ns | 0.536 ns | 1.00 | 0.00 | 0.0067 | - | 56 B |
| ToList | 0 | 31.51 ns | 0.485 ns | 0.405 ns | 1.06 | 0.02 | 0.0105 | - | 88 B |
| | | | | | | | | | |
| ToArray | 1 | 37.36 ns | 0.314 ns | 0.294 ns | 1.00 | 0.00 | 0.0114 | - | 96 B |
| ToList | 1 | 36.75 ns | 0.605 ns | 0.537 ns | 0.98 | 0.01 | 0.0153 | - | 128 B |
| | | | | | | | | | |
| ToArray | 6 | 100.05 ns | 1.522 ns | 1.349 ns | 1.00 | 0.00 | 0.0286 | - | 240 B |
| ToList | 6 | 85.16 ns | 0.808 ns | 0.756 ns | 0.85 | 0.01 | 0.0267 | - | 224 B |
| | | | | | | | | | |
| ToArray | 10 | 137.20 ns | 2.766 ns | 2.452 ns | 1.00 | 0.00 | 0.0372 | - | 312 B |
| ToList | 10 | 123.05 ns | 2.198 ns | 1.949 ns | 0.90 | 0.01 | 0.0372 | - | 312 B |
| | | | | | | | | | |
| ToArray | 42 | 398.25 ns | 6.583 ns | 5.836 ns | 1.00 | 0.00 | 0.0877 | - | 736 B |
| ToList | 42 | 352.04 ns | 4.976 ns | 4.411 ns | 0.88 | 0.02 | 0.0887 | - | 744 B |
| | | | | | | | | | |
| ToArray | 100 | 730.80 ns | 6.501 ns | 6.081 ns | 1.00 | 0.00 | 0.1488 | - | 1,248 B |
| ToList | 100 | 705.49 ns | 9.947 ns | 9.305 ns | 0.97 | 0.01 | 0.1526 | - | 1,280 B |
| | | | | | | | | | |
| ToArray | 1337 | 8,023.57 ns | 147.388 ns | 137.867 ns | 1.00 | 0.00 | 1.6785 | 0.0458 | 14,056 B |
| ToList | 1337 | 7,980.27 ns | 138.469 ns | 122.749 ns | 1.00 | 0.02 | 1.9989 | 0.1221 | 16,736 B |
| | | | | | | | | | |
| ToArray | 10000 | 57,037.19 ns | 510.492 ns | 452.538 ns | 1.00 | 0.00 | 12.6343 | 1.7700 | 106,280 B |
| ToList | 10000 | 57,728.15 ns | 583.353 ns | 517.127 ns | 1.01 | 0.01 | 15.5640 | 5.1270 | 131,496 B |
For reference, following below is the original answer which unfortunately performed a benchmark on a very particular case only, which avoided intermediate resize and copy operations.
ORIGINAL ANSWER:
It's 2020 outside and everyone is using .NET Core 3.1 so I decided to run some benchmarks with Benchmark.NET.
TL;DR: ToArray() is better performance-wise and does a better job conveying intent if you're not planning to mutate the collection.
EDIT: as seen from the comments, these benchmarks might not be indicative because Enumerable.Range(...) returns an IEnumerable<T> that has information about the size of the sequence, which is subsequently used in an optimization by ToArray() to pre-allocate an array of the correct size. Consider testing performance manually for your exact scenario.
[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>();
}
The results are:
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)