Cloning List<T>
Asked Answered
S

8

27

I thought that to clone a List you would just call:

List<int> cloneList = new List<int>(originalList);

But I tried that in my code and I seem to be getting effects that imply the above is simply doing:

cloneList = originalList... because changes to cloneList seem to be affecting originalList.

So what is the way to clone a List?

EDIT:

I am thinking of doing something like this:

public static List<T> Clone<T>(this List<T> originalList) where T : ICloneable
{
    return originalList.ConvertAll(x => (T) x.Clone());
}

EDIT2:

I took the deep copy code suggested by Binoj Antony and created this extension method:

public static T DeepCopy<T>(this T original) where T : class
{
    using (MemoryStream memoryStream = new MemoryStream())
    {
        BinaryFormatter binaryFormatter = new BinaryFormatter();
        binaryFormatter.Serialize(memoryStream, original);
        memoryStream.Seek(0, SeekOrigin.Begin);
        return (T)binaryFormatter.Deserialize(memoryStream);
    }
}

EDIT3:

Now, say the items in the List are structs. What then would result if I called?:

List<StructType> cloneList = new List<StructType>(originalList);

I am pretty sure than I would get a List filled with new unique items, correct?

Susiesuslik answered 6/2, 2009 at 7:41 Comment(7)
Also re the edit - note that ICloneable is a: poorly supported, and b: poorly specified (deep vs shallow) - it isn't seen hugely in everyday usage as a result.Bannerman
Re the edit - originalList.ConvertAll(item => (T)item.Clone()) would be more efficient - it can set the size correctly without having to re-allocate the list to fit.Bannerman
thanks for both points, they are notedSusiesuslik
You should really think about this as a system and implement ICloneable in a generic fashion. You can create an extension method for objects that implements ICloneable and clone them that way. If no such method exist fallback to object serialization but I would really recommend against it.Whelan
Which one have you ended up using?Tetryl
maybe change the last line to return binaryFormatter.Deserialize(memoryStream) as T;Tetryl
DeepCloner by force-net is extremely fast (2021): github.com/force-net/DeepClonerPorcelain
T
16

You can use the below code to make a deep copy of the list or any other object supporting serialization:

Also you can use this for any version of .NET framework from v 2.0 and above, and the similar technique can be applied (removing the usage of generics) and used in 1.1 as well

public static class GenericCopier<T>
{
    public static T DeepCopy(object objectToCopy)
    {
        using (MemoryStream memoryStream = new MemoryStream())
        {
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            binaryFormatter.Serialize(memoryStream, objectToCopy);
            memoryStream.Seek(0, SeekOrigin.Begin);
            return (T) binaryFormatter.Deserialize(memoryStream);
        }
    }
}

You can call it by using

List<int> deepCopiedList = GenericCopier<List<int>>.DeepCopy(originalList);

Full code to test if this works:

static void Main(string[] args)
{
    List<int> originalList = new List<int>(5);
    Random random = new Random();
    for(int i = 0; i < 5; i++)
    {
        originalList.Add(random.Next(1, 100));
        Console.WriteLine("List[{0}] = {1}", i, originalList[i]);
    }
    List<int> deepCopiedList = GenericCopier<List<int>>.DeepCopy(originalList);
    for (int i = 0; i < 5; i++)
        Console.WriteLine("deepCopiedList[{0}] value is {1}", i, deepCopiedList[i]);
}
Tetryl answered 6/2, 2009 at 8:10 Comment(5)
Does every class that gets copied have to have the [Serializable] attribute ?Susiesuslik
I just updated the reply with full code to test your scenario, it works at my end :)Tetryl
I think it would be worth updating the answer from "any other object" to "any other object supporting serialization".Ician
Binoj, check out my extension method version I added to the original postSusiesuslik
Thanks for this - the code worked perfectly on a problem that was just driving me crazy!Passepartout
B
28

This would work...

List<Foo> cloneList = new List<Foo>(originalList);

When you say "because changes to cloneList seem to be affecting originalList." - do you mean changes to the list, or changes to the items...

Adding / removing / swapping items is changing the list - so if we do:

cloneList.Add(anotherItem);

you should find that cloneList is longer than originalList. However, if the contents are reference-types (classes), then both lists are still pointing at the same underlying objects - so if we do:

cloneList[0].SomeObjectProperty = 12345;

then this will also show against originalList[0].SomeObjectProperty - there is only a single object (shared between both lists).

If this is the problem, you will have to clone the objects in the list - and then you get into the whole deep vs shallow issue... is this the problem?

For a shallow copy, you might be able to use something very much like the answer here - simply with TTo = TFrom (perhaps simplify to a single T).

Bannerman answered 6/2, 2009 at 7:42 Comment(2)
so the fact that it SEEMS to not be working, means I am doing something wrong, because the way you mentioned is the way I am currently doing itSusiesuslik
ahhh... I see. Thank you. I misunderstood how shallow a shallow copy really was :)Susiesuslik
T
16

You can use the below code to make a deep copy of the list or any other object supporting serialization:

Also you can use this for any version of .NET framework from v 2.0 and above, and the similar technique can be applied (removing the usage of generics) and used in 1.1 as well

public static class GenericCopier<T>
{
    public static T DeepCopy(object objectToCopy)
    {
        using (MemoryStream memoryStream = new MemoryStream())
        {
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            binaryFormatter.Serialize(memoryStream, objectToCopy);
            memoryStream.Seek(0, SeekOrigin.Begin);
            return (T) binaryFormatter.Deserialize(memoryStream);
        }
    }
}

You can call it by using

List<int> deepCopiedList = GenericCopier<List<int>>.DeepCopy(originalList);

Full code to test if this works:

static void Main(string[] args)
{
    List<int> originalList = new List<int>(5);
    Random random = new Random();
    for(int i = 0; i < 5; i++)
    {
        originalList.Add(random.Next(1, 100));
        Console.WriteLine("List[{0}] = {1}", i, originalList[i]);
    }
    List<int> deepCopiedList = GenericCopier<List<int>>.DeepCopy(originalList);
    for (int i = 0; i < 5; i++)
        Console.WriteLine("deepCopiedList[{0}] value is {1}", i, deepCopiedList[i]);
}
Tetryl answered 6/2, 2009 at 8:10 Comment(5)
Does every class that gets copied have to have the [Serializable] attribute ?Susiesuslik
I just updated the reply with full code to test your scenario, it works at my end :)Tetryl
I think it would be worth updating the answer from "any other object" to "any other object supporting serialization".Ician
Binoj, check out my extension method version I added to the original postSusiesuslik
Thanks for this - the code worked perfectly on a problem that was just driving me crazy!Passepartout
I
12

I doubt that your actual example would have problems, because int is a value type. For instance:

using System;
using System.Collections.Generic;

class Test
{
    static void Main()
    {
        List<int> originalList = new List<int> { 5, 6, 7 };
        List<int> cloneList = new List<int>(originalList);

        cloneList.Add(8);
        cloneList[0] = 2;
        Console.WriteLine(originalList.Count); // Still 3
        Console.WriteLine(originalList[0]); // Still 5
    }
}

However, as Marc says, if your list contains mutable reference types, cloning the list will only take a shallow copy - if you mutate the objects that the lists refer to, those changes will be visible via both lists. Replacing elements in one list won't change the equivalent element in the other list though:

using System;
using System.Collections.Generic;

class Dummy
{
    public int Value { get; set; }

    public Dummy (int value)
    {
        this.Value = value;
    }
}

class Test
{
    static void Main()
    {
        List<Dummy> originalList = new List<Dummy> 
        {
            new Dummy(5),
            new Dummy(6),
            new Dummy(7)
        };

        List<Dummy> cloneList = new List<Dummy>(originalList);

        cloneList[0].Value = 1;
        cloneList[1] = new Dummy(2);
        Console.WriteLine(originalList[0].Value); // Changed to 1
        Console.WriteLine(originalList[1].Value); // Still 6
    }
}

To take a "deep clone" of a list where the element type implements ICloneable, use:

List<Foo> cloneList = originalList.ConvertAll(x => (Foo) x.Clone());

However, the real depth of this clone will depend on the implementation of ICloneable in the element type - ICloneable is generally regarded as a Bad Thing because its contract is so vague.

Ician answered 6/2, 2009 at 8:1 Comment(1)
I was thinking of doing something like what I just put in my edit to the original post.Susiesuslik
L
3

Using the List constructor with the original list as parameter will work if the underlying type of the list is a value type. For reference type List elements, I think you want to deep copy them.

You could do something like this:

(Assuming that the underlying type implements ICloneable)

originalList.ForEach((item) =>
                       {
                         cloneList.Add((ICloneable)item.Clone());
                       }
                     );

Or using some LINQ:

var cloneList = originalList.Select(item => (ICloneable)item.Clone()).ToList();
Levator answered 6/2, 2009 at 7:58 Comment(2)
nice, now we're getting somewhereSusiesuslik
For clarity, ICloneable is not stated as either deep or shallow - it is often shallow. Likewise, struct blits are shallow too. Note also that ICloneable is pretty poorly supported.Bannerman
W
2

I vote to not rely on object serialization. It's costly and bad practice.

public static TObj CloneObject<TObj>(this TObj obj)
    where TObj : ICloneable
{
    return (TObj)obj.Clone();
}

The above method is a lot more elegant, and you should really care to implement a clonable interface if you need one. You could also make it generic.

public interface ICloneable<T> : IClonable
{
    T CloneObject();
}

Optionally, you could refrain from using the IClonable interface as a base type as it's poorly maintained. The method name has to change to because you can't do overloads on return types.

public static List<T> CloneList(this List<T> source)
    where TObj : ICloneable
{
    return source.Select(x=>x.CloneObject()).ToList();
}

It's as simple as that.

Maybe your problem can be solved though using value types instead. They are always pass-by-copy. So you never have to clone anything as long as your data structure is by value.

Whelan answered 6/2, 2009 at 9:18 Comment(3)
Have you done any tests on this code to verify that this performs better than the object serialization?Tetryl
Implementing deep copy cloning without using serialization is nearly impossible, I only ever recommend trying to implement ICloneable yourself if your object is 100% fully a property bag of primitive types that you can use the MemberwiseClone() method to copy your object.Furnace
Since kind of object serialization you're doing is mindless reflection it will be slowest kind of serialization, if you implement a cloning strategy it will be faster, because it wont rely on reflection. In contrast you'll need all objects in your object graph to be clonable for it to work.Whelan
E
1

It specifically says here that items are copied over to your new list. So yes, that should work. With value types you'll get complete independence. But remember, with reference types, the lists will be independent but they'll be pointing to the same objects. You'll need to deep copy the list.

Edinburgh answered 6/2, 2009 at 7:49 Comment(0)
W
0

            List list = new List ();
            List clone = new List (list);
            list.Add (new int ());
            Debug.Assert (list != clone);
            Debug.Assert (list.Count == 1);
            Debug.Assert (clone.Count == 0);

This code is perfectly working as intended for me. Are you maybe changing the objects IN the list? The list items won't get cloned by new List(oldList).

Which answered 6/2, 2009 at 7:51 Comment(3)
that is what I just learned, thank you :) I thought that the list items would copy. we live and we learn.Susiesuslik
if I implement IClonable for the class List<T> is holding, will this method them Clone the items in the list?Susiesuslik
I'm pretty sure it won't. new List(oldList) will just create a new list and copy the references(!) to oldList's elements.Which
F
0

I have to add: If you're going to use serialization to facilitate deep copying, why would you clone each individual item? Just clone the entire original list to start with.

Unless you have logic in place that you only clone nodes that meet certain criteria then do it node by node.

Furnace answered 6/2, 2009 at 14:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.