问题
Consider you have to convert mylist
of type List<T>
to List<Base>
where T
is subclass of Base
Are these solutions the same? Which has better performances, and why? When should I prefer using the first or the second?
return mylist.Cast<Base>().ToList();
return mylist.ConvertAll(x => (Base)x);
Maybe the second solution could be better because mylist is converted directly.
In the first solution the list is converted to IEnumerable, then to list, but I'm not sure.
回答1:
TL;DR: ConvertAll
makes 1 memory allocation, but .Cast.ToList
more than one in most cases.
Most LINQ extensions (like .Cast<T>()
) result in a deferred execution IEnumerable<T>
that can't be cast to ICollection<T>
(can't get the .Count
of the result).
When the result can be cast to ICollection<T>
, .ToList and .ToArray can make just one memory allocation to copy the elements, but when it can't:
- initially 4 element buffer array is allocated for non-empty source
- when space is needed for more elements, new array is allocated with double the size of the previous one
- elements are copied from the old array to the new one, and the old array is later deallocated by the garbage collector.
Update
Surprisingly, the difference doesn't seem nowhere near as significant as I expected:
method elapsed ratio count
Cast.ToList 00:00:14.4487329 1.3719890831991 123456789
ConvertAll 00:00:10.5312302 0.728868773261865
Cast.ToList 00:00:01.4959734 1.50233158227713 12345678
ConvertAll 00:00:00.9957678 0.665632016125407
Cast.ToList 00:00:00.1252968 2.45948743599897 1234567
ConvertAll 00:00:00.0509442 0.40658878161491
Cast.ToList 00:00:00.0082611 3.99145006839945 123456
ConvertAll 00:00:00.0020697 0.250535515380002
Cast.ToList 00:00:00.0008097 0.620558719826417 12345
ConvertAll 00:00:00.0013049 1.61145104895105
Cast.ToList 00:00:00.0001812 0.193207547169811 1234
ConvertAll 00:00:00.0009378 5.17578125
Cast.ToList 00:00:00.0001433 0.149501661129568 123
ConvertAll 00:00:00.0009587 6.68888888888889
So, race your horses!
int c = 123; var L = Enumerable.Range(0, c).ToList();
GC.Collect(); var sw1 = Stopwatch.StartNew(); L.Cast<object>().ToList(); sw1.Stop();
GC.Collect(); var sw2 = Stopwatch.StartNew(); L.ConvertAll(i => (object)i); sw2.Stop();
MessageBox.Show($"Cast.ToList\t{sw1.Elapsed}\t{(double)sw1.ElapsedTicks / sw2.ElapsedTicks}\n" +
$"ConvertAll \t{sw2.Elapsed}\t{(double)sw2.ElapsedTicks / sw1.ElapsedTicks}");
来源:https://stackoverflow.com/questions/42427912/linq-cast-or-cast-inside-convertall-for-a-list