Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ToList with Capacity?

Tags:

c#

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).

like image 363
Sergey Slepov Avatar asked Nov 09 '17 17:11

Sergey Slepov


People also ask

Does ToList decrease performance?

ToList() does have a performance impact, it is an O(n) operation though it will likely only require attention in performance critical operations.

What does ToList () do?

The tolist() function is used to convert a given array to an ordinary list with the same items, elements, or values.

Why do we use ToList () in C#?

The ToList<TSource>(IEnumerable<TSource>) method forces immediate query evaluation and returns a List<T> that contains the query results. You can append this method to your query in order to obtain a cached copy of the query results.

Is ToList a deep copy?

ToList() makes a shallow copy. The references are copied, but the new references still point to the same instances as the original references point to.


1 Answers

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

}
like image 85
Sergey Kalinichenko Avatar answered Sep 28 '22 23:09

Sergey Kalinichenko