19

I'm trying to make a deep copy of a generic list, and am wondering if there is any other way then creating the copying method and actually copying over each member one at a time. I have a class that looks somewhat like this:

public class Data
{            
    private string comment;
    public string Comment
    {
        get { return comment; }
        set { comment = value; }
    }

    private List<double> traceData;
    public List<double> TraceData
    {
        get { return traceData; }
        set { traceData = value; }
    }
}

And I have a list of the above data, i.e List<Data>. What I'm trying to do is plot a trace data of the subset of List onto a graph, possibly with some scaling or sweeping on the data. I obviously don't need to plot everything in the list because they don't fit into the screen.

I initially tried getting the subset of the list using the List.GetRange() method, but it seems that the underneath List<double> is being shallow copied instead of deep copied. When I get the subset again using List.GetRange(), I get previously modified data, not the raw data retrieved elsewhere.

Can anyone give me a direction on how to approach this? Thanks a lot.

thomas1234
  • 599
  • 3
  • 6
  • 15
  • Skeet edited but didn't know the answer? (8-O – Brad Nov 19 '10 at 16:02
  • 1
    Maybe I'm missing something, but what would be a "Deep copy" of a `List`? It's a list of numbers, it's not like a list of Button classes or something that has members that might need to be copied? – CodingGorilla Nov 19 '10 at 16:04
  • I assume that he means he wants to make a deep copy of each `Data` object, implying that he needs to copy the list rather than just copy the reference. – mqp Nov 19 '10 at 16:08
  • Yes, sorry about the confusing words. Is there a way to deep copy only a setset of the List object? Thanks. – thomas1234 Nov 19 '10 at 16:08
  • `Data.TraceData.GetRange()` creates a copy of the specified subset - is that an option? (Here it doesn't matter that `GetRange` creates a shallow copy since doubles are value types.) – Jeff Sternal Nov 19 '10 at 16:11
  • I need get call the GetRange() on List, which then would hopefully deep-copy the traceData underneath. I am looking into more details from the answers given. Thanks a lot guys! – thomas1234 Nov 19 '10 at 16:13
  • @Coding Gorilla: A "deep copy" of a List would be a List with a new array of data elements containing the same numbers as were present in the original. Note that merely deriving a class from List, and having an object of that class call MemberwiseClone on itself, will not produce a usable List, even if T is a value type. – supercat Jul 19 '11 at 22:26
  • possible duplicate of [How do I clone a generic list in C#?](http://stackoverflow.com/questions/222598/how-do-i-clone-a-generic-list-in-c) – Liam Mar 19 '14 at 17:05

9 Answers9

11

The idiomatic way to approach this in C# is to implement ICloneable on your Data, and write a Clone method that does the deep copy (and then presumably a Enumerable.CloneRange method that can clone part of your list at once.) There isn't any built-in trick or framework method to make it easier than that.

Unless memory and performance are a real concern, I suggest that you try hard to redesign it to operate on immutable Data objects, though, instead. It'll wind up much simpler.

mqp
  • 70,359
  • 14
  • 95
  • 123
  • 2
    +1: immutable is really the only way to go :) If the user doesn't need indexed access to the `List`, then a simple immutable stack is more than adequate enough. – Juliet Nov 19 '10 at 17:18
  • 2
    [IClonable is pretty much considered a bad idea these days](http://stackoverflow.com/questions/536349/why-no-icloneablet) – Liam Mar 19 '14 at 17:05
6

You can try this

    public static object DeepCopy(object obj)
    {
        if (obj == null)
            return null;
        Type type = obj.GetType();

        if (type.IsValueType || type == typeof(string))
        {
            return obj;
        }
        else if (type.IsArray)
        {
            Type elementType = Type.GetType(
                 type.FullName.Replace("[]", string.Empty));
            var array = obj as Array;
            Array copied = Array.CreateInstance(elementType, array.Length);
            for (int i = 0; i < array.Length; i++)
            {
                copied.SetValue(DeepCopy(array.GetValue(i)), i);
            }
            return Convert.ChangeType(copied, obj.GetType());
        }
        else if (type.IsClass)
        {

            object toret = Activator.CreateInstance(obj.GetType());
            FieldInfo[] fields = type.GetFields(BindingFlags.Public |
                        BindingFlags.NonPublic | BindingFlags.Instance);
            foreach (FieldInfo field in fields)
            {
                object fieldValue = field.GetValue(obj);
                if (fieldValue == null)
                    continue;
                field.SetValue(toret, DeepCopy(fieldValue));
            }
            return toret;
        }
        else
            throw new ArgumentException("Unknown type");
    }

Thanks to DetoX83 article on code project.

Suresh Kumar Veluswamy
  • 4,193
  • 2
  • 20
  • 35
  • 2
    Almost flawless. But as it was in my case, you have arrays of objects outside either mscorlib or the current assembly, you will need to use `elementType = Type.GetType(type.AssemblyQualifiedName.Replace("[]", string.Empty));` – makoshichi Feb 13 '15 at 12:57
5

If IClonable way is too tricky for you. I suggest converting to something and back. It can be done with BinaryFormatter or a Json Converter like Servicestack.Text since it is the fastest one in .Net.

Code should be something like this:

MyClass mc = new MyClass();
string json = mc.ToJson();
MyClass mcCloned = json.FromJson<MyClass>();

mcCloned will not reference mc.

Xelom
  • 1,615
  • 1
  • 13
  • 23
3

The most easiest (but dirty) way is to implement ICloneable by your class and use next extension method:

public static IEnumerable<T> Clone<T>(this IEnumerable<T> collection) where T : ICloneable
{
    return collection.Select(item => (T)item.Clone());
}

Usage:

var list = new List<Data> { new Data { Comment = "comment", TraceData = new List { 1, 2, 3 } };
var newList = list.Clone();
abatishchev
  • 98,240
  • 88
  • 296
  • 433
  • 4
    ICloneable is like a rash, start scratching and before you know it it's everywhere. – jonnii Nov 19 '10 at 16:06
  • 2
    Should be noted that `item.Clone()` does not guarantee a deep copy, and the `.MemberwiseClone()` method create a shallow copy of internal members. See msdn: http://msdn.microsoft.com/en-us/library/system.object.memberwiseclone.aspx – Juliet Nov 19 '10 at 16:08
  • 1
    @Brad, @Juliet: That's why I called it dirty. – abatishchev Nov 19 '10 at 16:10
1

another thing you can do is mark your class as serializable and use binary serialization. Here is a working example

   public class Program
    {
        [Serializable]
        public class Test
        {
            public int Id { get; set; }
            public Test()
            {

            }
        }

        public static void Main()
        {   
            //create a list of 10 Test objects with Id's 0-10
            List<Test> firstList = Enumerable.Range(0,10).Select( x => new Test { Id = x } ).ToList();
            using (var stream = new System.IO.MemoryStream())

            {
                 var binaryFormatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                 binaryFormatter.Serialize(stream, firstList); //serialize to stream
                 stream.Position = 0;
                 //deserialize from stream.
                 List<Test> secondList = binaryFormatter.Deserialize(stream) as List<Test>; 
            }


            Console.ReadKey();
        }
    }
Stan R.
  • 15,757
  • 4
  • 50
  • 58
  • Most of the time, I'd recommend implementing a deep copy by hand if you can since serialization isn't exactly known for its blazing speed. However, I've actually used this style in the past with *enormous* trees of mutable data, and it works just fine for practical use. – Juliet Nov 20 '10 at 17:25
  • @Juliet, thanks, I was just providing an alternative solution to the ones that were already posted. – Stan R. Nov 20 '10 at 17:29
0
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DeepListCopy_testingSome
{
    class Program
    {
        static void Main(string[] args)
        {
            List<int> list1 = new List<int>();
            List<int> list2 = new List<int>();

            //populate list1
            for (int i = 0; i < 20; i++)
            {
                list1.Add(1);
            }

            ///////
            Console.WriteLine("\n int in each list1 element is:\n");
            ///////

            foreach (int i in list1)
            {
                Console.WriteLine(" list1 elements: {0}", i);
                list2.Add(1);
            }

            ///////
            Console.WriteLine("\n int in each list2 element is:\n");
            ///////

            foreach (int i in list2)
            {
                Console.WriteLine(" list2 elements: {0}", i);
            }

            ///////enter code here

            for (int i = 0; i < list2.Count; i++)
            {
                list2[i] = 2;
            }



            ///////
            Console.WriteLine("\n Printing list1 and list2 respectively to show\n"
                            + " there is two independent lists,i e, two differens"
                            + "\n memory locations after modifying list2\n\n");
            foreach (int i in list1)
            {
                Console.WriteLine(" Printing list1 elements: {0}", i);
            }

            ///////
            Console.WriteLine("\n\n");
            ///////

            foreach (int i in list2)
            {
                Console.WriteLine(" Printing list2 elements: {0}", i);
            }

            Console.ReadKey();
        }//end of Static void Main
    }//end of class
}
Adrian Sanguineti
  • 2,455
  • 1
  • 27
  • 29
  • Try using a collection of objects that are not primitives. Then your example will not work. Edit: Well now I see you are not even populating list 2 with list 1! This is not related to the question.. – Tadija Bagarić Feb 27 '17 at 15:58
0

If you make your objects immutable you don't need to worry about passing around copies of them, then you could do something like:

var toPlot = list.Where(d => d.ShouldBePlotted());
jonnii
  • 28,019
  • 8
  • 80
  • 108
0

Since your collection is mutable, you need to implement the deep copy programmatically:

public class Data
{
    public string Comment { get; set; }
    public List<double> TraceData { get; set; }

    public Data DeepCopy()
    {
        return new Data
        {
            Comment = this.Comment, 
            TraceData = this.TraceData != null
                ? new List<double>(this.TraceData)
                : null;
        }
    }
}

The Comment field can be shallow copied because its already an immutable class. You need to create a new list for TraceData, but the elements themselves are immutable and require no special handling to copy them.

When I get the subset again using List.GetRange(), I get previously modified data, not the raw data retrieved elsewhere.

Use your new DeepCopy method as such:

var pointsInRange = dataPoints
    .Select(x => x.DeepCopy())
    .GetRange(start, length);
Juliet
  • 80,494
  • 45
  • 196
  • 228
0

One quick and generic way to deeply serialize an object is to use JSON.net. The following extension method allows serializing of a list of any arbitrary objects, but is able to skip Entity Framework navigation properties, since these may lead to circular dependencies and unwanted data fetches.

Method

public static List<T> DeepClone<T>(this IList<T> list, bool ignoreVirtualProps = false)
{
    JsonSerializerSettings settings = new JsonSerializerSettings();
    if (ignoreVirtualProps)
    {
        settings.ContractResolver = new IgnoreNavigationPropsResolver();
        settings.PreserveReferencesHandling = PreserveReferencesHandling.None;
        settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
        settings.Formatting = Formatting.Indented;
    }

    var serialized = JsonConvert.SerializeObject(list, settings);
    return JsonConvert.DeserializeObject<List<T>>(serialized);
}

Usage

var clonedList = list.DeepClone();

By default, JSON.NET serializes only public properties. If private properties must be also cloned, this solution can be used.

This method allows for quick (de)serialization of complex hierarchies of objects.

Community
  • 1
  • 1
Alexei - check Codidact
  • 22,016
  • 16
  • 145
  • 164