Reputation: 2141
When we do .ToList()
for an IEnumerable
, the list can potentially reallocate while scanning the IEnumerable
because it doesn't know the size upfront. If the size is known, is there a simple way to avoid the performance penalty? Something to the effect of initializing a List
with the required capacity and then copying the IEnumerable
into it? Ideally something as simple as .ToList(capacity)
(which doesn't exist).
Upvotes: 8
Views: 1594
Reputation: 727107
In cases when the capacity is part of IEnumerable<T>
that is also an ICollection<T>
, the library will allocate at the correct capacity.
Here is a reference implementation of List<T>(IEnumerable<T> source)
, which is invoked when you call ToList()
:
public List(IEnumerable<T> collection) {
if (collection==null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
Contract.EndContractBlock();
ICollection<T> c = collection as ICollection<T>;
if( c != null) {
int count = c.Count;
if (count == 0) {
_items = _emptyArray;
} else {
_items = new T[count];
c.CopyTo(_items, 0);
_size = count;
}
} else {
_size = 0;
_items = _emptyArray;
// This enumerable could be empty. Let Add allocate a new array, if needed.
// Note it will also go to _defaultCapacity first, not 1, then 2, etc.
using(IEnumerator<T> en = collection.GetEnumerator()) {
while(en.MoveNext()) {
Add(en.Current);
}
}
}
}
Note how the constructor behaves when collection
implements ICollection<T>
: rather than iterating the content and calling Add
for each item, it allocates the internal _items
array, and copies the content into it without reallocations.
In situations when the capacity is not embedded in class implementing IEnumerable<T>
, you can easily define one yourself, using a combination of standard methods:
public static class ToListExtension {
public static List<T> ToList<T>(this IEnumerable<T> source, int capacity)
{
var res = new List<T>(capacity);
res.AddRange(source);
return res;
}
}
Upvotes: 14