tags:

views:

808

answers:

8
+12  Q: 

Cloning List<T>

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?

+7  A: 

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

Marc Gravell
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 it
Alex Baranosky
I'll answer in an update
Marc Gravell
ahhh... I see. Thank you. I misunderstood how shallow a shallow copy really was :)
Alex Baranosky
+1  A: 

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.

colithium
A: 

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

mafutrct
that is what I just learned, thank you :) I thought that the list items would copy. we live and we learn.
Alex Baranosky
if I implement IClonable for the class List<T> is holding, will this method them Clone the items in the list?
Alex Baranosky
I'm pretty sure it won't. new List(oldList) will just create a new list and copy the references(!) to oldList's elements.
mafutrct
A: 

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();
CMS
nice, now we're getting somewhere
Alex Baranosky
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.
Marc Gravell
+4  A: 

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.

Jon Skeet
I was thinking of doing something like what I just put in my edit to the original post.
Alex Baranosky
+8  A: 

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]);
}
Binoj Antony
thank you, that could prove useful
Alex Baranosky
Does every class that gets copied have to have the [Serializable] attribute ?
Alex Baranosky
I just updated the reply with full code to test your scenario, it works at my end :)
Binoj Antony
I think it would be worth updating the answer from "any other object" to "any other object supporting serialization".
Jon Skeet
Binoj, check out my extension method version I added to the original post
Alex Baranosky
Good idea, just updated it Jon.
Binoj Antony
A: 

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 its poorly maintained. The method name has to change to because you cant 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.

EDIT

Maybe you're 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.

John Leidegren
Have you done any tests on this code to verify that this performs better than the object serialization?
Binoj Antony
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.
Chris Marisic
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.
John Leidegren
A: 

I have to add if your 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.

Chris Marisic