【问题标题】:Cloning List<T>克隆列表<T>
【发布时间】:2010-10-05 21:20:59
【问题描述】:

我认为克隆一个 List 你只需调用:

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

但我在我的代码中尝试过,我似乎得到了暗示上述内容只是在做的效果:

cloneList = originalList... 因为对 cloneList 的更改似乎正在影响 originalList。

那么克隆 List 的方法是什么?

编辑:

我正在考虑做这样的事情:

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

编辑2:

我采用了 Binoj Antony 建议的深拷贝代码并创建了这个扩展方法:

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:

现在,假设 List 中的项目是结构。如果我打电话会有什么结果?:

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

我很确定我会得到一个包含新的独特项目的列表,对吗?

【问题讨论】:

  • 还重新编辑 - 请注意 ICloneable 是 a:支持不佳,b:指定不佳(深与浅) - 结果在日常使用中并不常见。
  • 重新编辑 - originalList.ConvertAll(item => (T)item.Clone()) 会更有效 - 它可以正确设置大小而无需重新分配列表以适应。
  • 感谢这两点,他们已经注意到了
  • 您应该真正将其视为一个系统并以通用方式实现 ICloneable。您可以为实现 ICloneable 的对象创建扩展方法并以这种方式克隆它们。如果不存在这样的方法回退到对象序列化,但我真的建议不要这样做。
  • 你最后用的是哪一个?

标签: c# .net


【解决方案1】:

这会起作用...

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

当您说“因为对 cloneList 的更改似乎正在影响 originalList”时。 - 你的意思是改变list,还是改变items...

添加/删除/交换项目正在改变列表 - 所以如果我们这样做:

cloneList.Add(anotherItem);

您应该会发现cloneListoriginalList 长。但是,如果内容是引用类型(类),那么两个列表仍然指向相同的底层对象 - 所以如果我们这样做:

cloneList[0].SomeObjectProperty = 12345;

那么这也将针对 originalList[0].SomeObjectProperty 显示 - 只有一个对象(在两个列表之间共享)。

如果这是问题所在,您将不得不克隆列表中的 对象 - 然后您会陷入整个深浅问题...这是问题所在吗?

对于浅拷贝,您可能可以使用与答案 here 非常相似的内容 - 只需使用 TTo = TFrom(可能简化为单个 T)。

【讨论】:

  • 所以它似乎不起作用的事实意味着我做错了什么,因为你提到的方式就是我目前正在做的方式
  • 啊……我明白了。谢谢你。我误解了浅拷贝实际上有多浅:)
【解决方案2】:

您可以使用以下代码制作列表或任何其他支持序列化的对象的深层副本:

您也可以将它用于任何版本的 .NET 框架 v 2.0 及更高版本,并且可以应用类似的技术(删除泛型的使用)并在 1.1 中使用

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

你可以通过使用来调用它

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

测试这是否有效的完整代码:

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

【讨论】:

  • 是否每个被复制的类都必须具有 [Serializable] 属性?
  • 我刚刚用完整的代码更新了回复来测试你的场景,它在我的最后工作:)
  • 我认为值得将答案从“任何其他对象”更新为“任何其他支持序列化的对象”。
  • Binoj,查看我添加到原始帖子中的扩展方法版本
  • 谢谢你 - 代码完美地解决了一个让我发疯的问题!
【解决方案3】:

我怀疑您的 实际 示例会有问题,因为 int 是一个值类型。例如:

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

但是,正如 Marc 所说,如果您的列表包含可变引用类型,则克隆该列表只会获取 副本 - 如果您更改列表引用的对象,这些更改将可见通过这两个列表。但是,替换一个列表中的元素不会更改另一个列表中的等效元素:

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

要对元素类型实现ICloneable 的列表进行“深度克隆”,请使用:

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

然而,这个克隆的真正深度将取决于元素类型中ICloneable 的实现——ICloneable 通常被认为是一个坏事,因为它的契约是如此模糊。

【讨论】:

  • 我正在考虑做一些事情,就像我刚刚在对原始帖子的编辑中所做的那样。
【解决方案4】:

如果列表的基础类型是值类型,则使用带有原始列表作为参数的 List 构造函数将起作用。对于引用类型的 List 元素,我想你想深拷贝它们。

你可以这样做:

(假设底层类型实现了ICloneable)

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

或者使用一些LINQ:

var cloneList = originalList.Select(item => (ICloneable)item.Clone()).ToList();

【讨论】:

  • 很好,现在我们到了某个地方
  • 为了清楚起见,ICloneable 没有说明为深或浅 - 它通常是浅的。同样,struct blits 也很浅。另请注意,对 ICloneable 的支持很差。
【解决方案5】:

我投票不依赖对象序列化。这是昂贵且不好的做法。

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

上面的方法要优雅得多,如果你需要一个可克隆的接口,你应该非常小心。你也可以让它通用。

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

您可以选择避免使用IClonable 接口作为基本类型,因为它维护不善。方法名称必须更改为,因为您不能对返回类型进行重载。

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

就这么简单。

也许您的问题可以通过使用值类型来解决。他们总是通过副本。因此,只要您的数据结构是按值计算的,您就不必克隆任何东西。

【讨论】:

  • 您是否对此代码进行了任何测试以验证其性能是否优于对象序列化?
  • 在不使用序列化的情况下实现深拷贝克隆几乎是不可能的,我只建议您尝试自己实现 ICloneable 如果您的对象 100% 完全是可以使用 MemberwiseClone() 方法的原始类型的属性包复制您的对象。
  • 由于你正在做的对象序列化是无意识的反射,它将是最慢的序列化,如果你实施克隆策略,它会更快,因为它不会依赖反射。相反,您需要对象图中的所有对象都是可克隆的,它才能工作。
【解决方案6】:

它特别指出here 将项目复制到您的新列表中。所以是的,这应该有效。使用值类型,您将获得完全的独立性。但请记住,对于引用类型,列表将是独立的,但它们将指向相同的对象。您需要深复制列表。

【讨论】:

    【解决方案7】:
    
                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);
    

    这段代码完全符合我的预期。您是否可能更改列表中的对象?列表项不会new List(oldList) 克隆。

    【讨论】:

    • 这就是我刚刚学到的,谢谢 :) 我以为列表项会复制。我们生活,我们学习。
    • 如果我为 List 持有的类实现 IClonable,这个方法会克隆列表中的项目吗?
    • 我很确定它不会。 new List(oldList) 只会创建一个新列表并将引用(!)复制到 oldList 的元素。
    【解决方案8】:

    我必须补充:如果您要使用序列化来促进深度复制,为什么要克隆每个单独的项目?只需克隆整个原始列表即可。

    除非您有适当的逻辑,即您只克隆满足特定条件的节点,然后逐个节点地进行。

    【讨论】:

      猜你喜欢
      • 2014-11-25
      • 2012-10-07
      • 1970-01-01
      • 2013-01-06
      • 1970-01-01
      • 1970-01-01
      • 2011-04-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多