Deep copy of List<T>

我的未来我决定 提交于 2019-11-26 16:39:17

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.

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.

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.

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();

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

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());

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);
user7433127
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
}
Alexei

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.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!