webdad3
webdad3

Reputation: 9080

copying a list to a new list - More efficient & Best Practice

What is the appropriate way of copying a list to a new list? And What is the most efficient way of copying a list to a new list?

By efficient, not code efficiency, more in the behind the scenes framework sense.

List<String>List2 = List.ToList();

Or:

List<String>List2 = new List<String>();
foreach (string item in List)
{
 List2.Add(item);
}

Update:

What about more efficient IL code?

Upvotes: 12

Views: 18617

Answers (7)

Engineer
Engineer

Reputation: 8857

Answers that uses new (including e.g. ToList(), ToArray() which internally use new) when you need a list copy, cause heap allocations & consequent garbage collection. Those are astoundingly bad ways to get "the fastest possible list copy". So you can safely ignore most of the answers here.

new your lists once off only, at your application startup, ensuring your lists are maximally sized.

int MAX_SIZE = 100000;
List<int> list1 = new List<int>(MAX_SIZE);
List<int> list2 = new List<int>(MAX_SIZE);

Then do this at runtime wherever you need a full copy of the list:

list2.AddRange(list1);

List.AddRange() uses List.InsertRange() which uses Array.Copy() which uses an approach something like memcpy in C, i.e. it does bulk copies very fast.

The balance here will be choosing your MAX_SIZE carefully - not so big that it makes your copies slow, not so small that you exceed what your program is likely to need.

Upvotes: 0

Moo-Juice
Moo-Juice

Reputation: 38820

Given that List<T> has an IEnumerable<T> constructor, I would prefer this form:

List<string> newList = new List<string>(otherList);

Edit

And as Ondrej points out in the decompiled code below, the constructor of List<T> preallocates the size of the array and copies the contents over. This is going to be much quicker than creating a new list and then iterating over the other list adding items individually, especially as in your 2nd example you're not specifying how many items to preallocate.

Upvotes: 24

utter_step
utter_step

Reputation: 673

Tests shows, that the best perfomance have the .ToList() method (for a list with 21474836 elements it runs in about 48 ms on a laptop Core i5 CPU).

Every other method is slower, and the method, using .Add() is the worst, while talking about perfomance.

Here is some test code:

class Program
{
    static void Main()
    {
        var list = new List<int>();

        for (int i = 0; i < int.MaxValue / 100; i++)
        {
            list.Add(i);
        }

        TimeItAccurate(ListCopy_1, list, 10);
        TimeItAccurate(ListCopy_2, list, 10);
        TimeItAccurate(ListCopy_3, list, 10);
        TimeItAccurate(ListCopy_4, list, 10);
        TimeItAccurate(ListCopy_5, list, 10);
    }

    private static List<int> ListCopy_1(List<int> list)
    {
        var newList = list.ToList();

        return newList;
    }

    private static List<int> ListCopy_2(List<int> list)
    {
        var newList = new List<int>(list.Count);

        foreach (var i in list)
        {
            newList.Add(i);
        }

        return newList;
    }

    private static List<int> ListCopy_3(List<int> list)
    {
        var newList = new List<int>(list.ToArray());

        return newList;
    }

    private static List<int> ListCopy_4(List<int> list)
    {
        var newList = new List<int>(list.Count);

        newList.AddRange(list);

        return newList;
    }

    private static List<int> ListCopy_5(List<int> list)
    {
        var newList = new List<int>(list);

        return newList;
    }

    public static void TimeItAccurate<TIn, TResult>(Func<TIn, TResult> func, TIn argument, int iterationsCount)
    {
        #region Pre-heat
        for (int i = 0; i < 10; i++)
        {
            var t = func.Invoke(argument);
        }
        #endregion

        var stopwatch = new Stopwatch();
        var result = default(TResult);

        stopwatch.Start();
        for (int i = 0; i < iterationsCount; i++)
        {
            result = func.Invoke(argument);
        }
        stopwatch.Stop();

        Console.WriteLine("Result:\n{4}(...) == {0}\n\n{1} iterations done in {2} ms.\nAverage time: {3:f5} ms.",
            result,
            iterationsCount,
            stopwatch.ElapsedMilliseconds,
            stopwatch.ElapsedMilliseconds / (double)iterationsCount,
            func.Method.Name);
    }
}

And the results:

Result (.ToList()):
ListCopy_1(...) == System.Collections.Generic.List`1[System.Int32]

10 iterations done in 474 ms.
Average time: 47.40000 ms.

Result (for-cycle with .Add()):
ListCopy_2(...) == System.Collections.Generic.List`1[System.Int32]

10 iterations done in 1896 ms.
Average time: 189.60000 ms.

Result (ctor with .ToArray()):
ListCopy_3(...) == System.Collections.Generic.List`1[System.Int32]

10 iterations done in 981 ms.
Average time: 98.10000 ms.

Result (.AddRange()):
ListCopy_4(...) == System.Collections.Generic.List`1[System.Int32]

10 iterations done in 959 ms.
Average time: 95.90000 ms.

Result (new List<int>(list)):
ListCopy_5(...) == System.Collections.Generic.List`1[System.Int32]

10 iterations done in 480 ms.
Average time: 48.00000 ms.

Upvotes: 2

Andrew Whitaker
Andrew Whitaker

Reputation: 126072

In terms of efficiency, the first is going to be faster. List<T>'s underlying implementation is an ArrayList, so there's a chance you'll have to resize the underlying array when you call .Add.

On the other hand, .ToList can determine the correct initial size of the new List and avoid the reallocation operation that the foreach technique suffers from.

With this in mind I'd recommend .ToList. Less code and it will be faster.

Here's a simple program you can run to verify that ToList is indeed faster:

void Main()
{
    List<int> items = new List<int>();

    items = Enumerable.Range(0, 1000000).ToList();

    CopyWithToList(items);
    CopyWithForeach(items);

}

public void CopyWithToList<T>(List<T> list) 
{
    var sw = Stopwatch.StartNew();
    List<T> copy = list.ToList();
    sw.Stop();
    Console.WriteLine("CopyWithToList: {0}", sw.Elapsed);
}

public void CopyWithForeach<T>(List<T> list)
{
    var sw = Stopwatch.StartNew();
    List<T> copy = new List<T>();
    foreach (T item in list) {
        copy.Add(item);
    }
    sw.Stop();

    Console.WriteLine("CopyWithForeach: {0}", sw.Elapsed);
}

Upvotes: 3

Ondrej Svejdar
Ondrej Svejdar

Reputation: 22074

What ToList does (shortened):

public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)
{
    return new List<TSource>(source);
}

What List ctor does (shortened):

public List(IEnumerable<T> collection)
{
    ICollection<T> collection2 = collection as ICollection<T>;
    int count = collection2.Count;
    this._items = new T[count];
    collection2.CopyTo(this._items, 0);
    this._size = count;
}

So the ToList() is much more efficient - it does allocate the space first and then copy all items in one step.

Upvotes: 11

oleksii
oleksii

Reputation: 35925

You can use List<T> constructor that takes IEnumerable<T>

List<string> list1 = new List<string>();

// fill list1

List<string> list2 = new List<string>(list1);

Upvotes: 5

Casperah
Casperah

Reputation: 4554

I believe that the two examples are identical, the .ToList() implements probably the later.

The best performance would be something like this:

List<String> list2 = new List<String>(list.Count);
foreach(String item in list)
    list2.Add(item);

The important part is to create list2 with enough capacity to hold its content.

If you don't need to modify the either list afterwards, the a reference copy is all you need:

List<String> list2 = list;

Upvotes: 1

Related Questions