List<T>的深拷贝

19

我想要制作一个泛型列表的深拷贝,想知道是否有其他方法可以实现,而不必创建复制方法并逐一复制每个成员。 我有一个类看起来有点像这样:

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

我有一个上述数据的列表,即List<Data>。我想做的是将列表的子集的跟踪数据绘制到图形上,可能对数据进行一些缩放或扫描。显然,我不需要绘制列表中的所有内容,因为它们不能适应屏幕。

我最初尝试使用List.GetRange()方法获取列表的子集,但似乎底层的List<double>只进行了浅复制,而不是深复制。当我再次使用List.GetRange()获取子集时,我得到的是之前修改过的数据,而不是从其他地方检索到的原始数据。

有人可以指导我如何解决这个问题吗?非常感谢。


1
也许我错过了什么,但 List<double> 的“深拷贝”会是什么?它是一个数字列表,不像 Button 类的列表或者其他可能需要复制成员的列表。 - CodingGorilla
我认为他的意思是他想要对每个“Data”对象进行深拷贝,这意味着他需要复制列表而不仅仅是复制引用。 - mqp
Data.TraceData.GetRange() 创建指定子集的副本 - 这是一个选项吗?(由于双精度是值类型,因此 GetRange 创建浅表副本并不重要。) - Jeff Sternal
我需要在List<Data>上调用GetRange(),希望它能够深度复制traceData。我正在从给出的答案中寻找更多细节。非常感谢大家! - thomas1234
可能是如何在C#中克隆一个泛型列表?的重复问题。 - Liam
显示剩余3条评论
9个回答

11

在C#中处理这个问题的惯用方式是在你的Data上实现ICloneable,并编写一个执行深度复制的Clone方法(然后可能是一个Enumerable.CloneRange方法,可以一次性克隆列表的一部分)。除此之外,没有任何内置的技巧或框架方法可以使它比这更容易。

除非内存和性能是真正的问题,否则我建议你努力重新设计它,以便操作不可变的Data对象。这样会简单得多。


2
+1:不可变性确实是唯一的选择 :) 如果用户不需要对 List<double> 进行索引访问,那么一个简单的不可变栈已经足够了。 - Juliet
2
ICloneable在当今被认为是一个不好的想法。 - Liam

6
您可以尝试这个。
    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");
    }

感谢DetoX83在code project上发布的文章


2
几乎完美。但是像我这种情况,如果你有对象数组在mscorlib或当前程序集之外,你需要使用elementType = Type.GetType(type.AssemblyQualifiedName.Replace("[]", string.Empty)); - makoshichi

5
如果IClonable方法对你来说太棘手了,我建议将其转换为其他形式再转回来。可以使用BinaryFormatter或Json Converter(如Servicestack.Text),因为它是.Net中最快的转换器之一。
代码应该像这样:
MyClass mc = new MyClass();
string json = mc.ToJson();
MyClass mcCloned = json.FromJson<MyClass>();

mcCloned不会引用mc。


1
ServiceStack使用GPL许可证。您可能希望使用Json.Net。 - aloisdg

3
最简单(但不太规范)的方法是在您的类中实现ICloneable接口,并使用下一个扩展方法:
public static IEnumerable<T> Clone<T>(this IEnumerable<T> collection) where T : ICloneable
{
    return collection.Select(item => (T)item.Clone());
}

用法:

var list = new List<Data> { new Data { Comment = "comment", TraceData = new List { 1, 2, 3 } };
var newList = list.Clone();

4
ICloneable就像皮疹一样,开始抓痒了,不知不觉就无处不在了。 - jonnii
2
需要注意的是,item.Clone() 不保证深度复制,而 .MemberwiseClone() 方法只创建内部成员的浅表副本。请参阅 msdn:http://msdn.microsoft.com/en-us/library/system.object.memberwiseclone.aspx。 - Juliet
1
@Brad,@Juliet:这就是为什么我称它为“脏”的原因。 - abatishchev

1

你可以做的另一件事是将你的类标记为可序列化,并使用二进制序列化。这里有一个可行的例子。

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

大多数情况下,如果可以的话,我建议手动实现深拷贝,因为序列化并不以其惊人的速度而闻名。然而,在过去,我实际上使用了这种风格处理了包含巨大可变数据树的情况,并且在实际使用中它完全可行。 - Juliet
@Juliet,谢谢,我只是提供了一个与已发布的解决方案不同的替代方案。 - Stan R.

0
如果您将对象设置为不可变的,就不必担心传递它们的副本,然后您可以执行以下操作:
var toPlot = list.Where(d => d.ShouldBePlotted());

0

由于您的集合是可变的,因此您需要编程实现深层复制:

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

Comment字段可以进行浅复制,因为它已经是一个不可变类。您需要为TraceData创建一个新列表,但元素本身是不可变的,无需特殊处理即可复制它们。

当我使用List.GetRange()再次获取子集时,我得到的是先前修改过的数据,而不是在其他地方检索到的原始数据。

使用您的新DeepCopy方法如下:

var pointsInRange = dataPoints
    .Select(x => x.DeepCopy())
    .GetRange(start, length);

0

深度序列化对象的一种快速通用方法是使用JSON.net。以下扩展方法允许序列化任意对象列表,但能够跳过Entity Framework导航属性,因为这些可能会导致循环依赖和不必要的数据获取。

方法

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

使用方法

var clonedList = list.DeepClone();

默认情况下,JSON.NET 仅序列化公共属性。如果还需要克隆私有属性,可以使用this solution

该方法允许快速(反)序列化 复杂对象层次结构


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
}

尝试使用非基本类型的对象集合,那样你的示例就不会起作用了。编辑:现在我看到你甚至没有用列表1填充列表2!这与问题无关。 - Tadija Bagarić

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接