82

在下面的一段代码中,

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

namespace clone_test_01
{

    public partial class MainForm : Form
    {

        public class Book
        {
            public string title = "";

            public Book(string title)
            {
                this.title = title;
            }
        }


        public MainForm()
        {
            InitializeComponent();

            List<Book> books_1 = new List<Book>();
            books_1.Add(  new Book("One")  );
            books_1.Add(  new Book("Two")  );
            books_1.Add(  new Book("Three")  );
            books_1.Add(  new Book("Four")  );

            List<Book> books_2 = new List<Book>(books_1);

            books_2[0].title = "Five";
            books_2[1].title = "Six";

            textBox1.Text = books_1[0].title;
            textBox2.Text = books_1[1].title;
        }
    }

}

我使用一个Book对象类型来创建一个List<T>,并用一些项目填充它,给它们一个唯一的标题(从“一”到“五”)。

然后我创建List<Book> books_2 = new List<Book>(books_1).

从这一点来看,我知道它是 list 对象的克隆,但是来自book_2的 book 对象仍然是来自 book 对象的引用books_1。通过对 的前两个元素进行更改books_2,然后检查 a 中的相同元素来book_1证明这一点TextBox

books_1[0].title and books_2[1].title确实已更改为 的新值books_2[0].title and books_2[1].title

现在的问题

我们如何创建一个新的硬拷贝List<T>?这个想法是,books_1并且books_2变得完全独立于彼此。

我很失望微软没有像 Ruby 那样提供简洁、快速和简单的解决方案clone()

帮助者真正令人敬畏的是使用我的代码并使用可行的解决方案对其进行更改,以便可以编译和工作。我认为这将真正帮助新手了解为这个问题提供的解决方案。

编辑:请注意,Book该类可能更复杂并具有更多属性。我试图让事情变得简单。

4

11 回答 11

124

您需要创建新Book对象,然后将它们放入一个新的List

List<Book> books_2 = books_1.Select(book => new Book(book.title)).ToList();

更新:稍微简单...List<T>有一个方法调用ConvertAll,返回一个新的列表:

List<Book> books_2 = books_1.ConvertAll(book => new Book(book.title));
于 2012-12-22T23:20:19.890 回答
41

创建一个ICloneable<T>您在Book类中实现的通用接口,以便该类知道如何创建自身的副本。

public interface ICloneable<T>
{
    T Clone();
}

public class Book : ICloneable<Book>
{
    public Book Clone()
    {
        return new Book { /* set properties */ };
    }
}

然后,您可以使用ConvertAllMark 提到的 linq 或方法。

List<Book> books_2 = books_1.Select(book => book.Clone()).ToList();

或者

List<Book> books_2 = books_1.ConvertAll(book => book.Clone());
于 2012-12-22T23:56:22.763 回答
21

我很失望微软没有像 Ruby 那样提供简洁、快速和简单的解决方案clone()

除了创建深拷贝外,它会创建浅拷贝。

使用深度复制,您必须始终小心,您究竟想要复制什么。可能的问题的一些例子是:

  1. 在对象图中循环。例如,Book有一个Author并且Author有一个他Book的列表。
  2. 引用一些外部对象。例如,一个对象可能包含Stream写入文件的 open。
  3. 事件。如果一个对象包含一个事件,几乎任何人都可以订阅它。如果订阅者是 GUI 之类的东西,这可能会变得特别成问题Window

现在,基本上有两种方法可以克隆一些东西:

  1. Clone()在需要克隆的每个类中实现一个方法。(也有ICloneable接口,但你不应该使用它;使用ICloneable<T>Trevor 建议的自定义接口是可以的。)如果你知道你所需要的只是为这个类的每个字段创建一个浅表副本,你可以使用MemberwiseClone()它来实现它. 作为替代方案,您可以创建一个“复制构造函数”:public Book(Book original).
  2. 使用序列化将您的对象序列化为 aMemoryStream然后反序列化它们。这需要您将每个类标记为[Serializable],并且还可以配置应该序列化的确切内容(以及如何)。但这更像是一种“快速而肮脏”的解决方案,而且很可能性能也会降低。
于 2012-12-23T00:19:14.263 回答
10

好,

如果将所有涉及的类标记为可序列化,则可以:

public static List<T> CloneList<T>(List<T> oldList)  
{  
BinaryFormatter formatter = new BinaryFormatter();  
MemoryStream stream = new MemoryStream();  
formatter.Serialize(stream, oldList);  
stream.Position = 0;  
return (List<T>)formatter.Deserialize(stream);      
} 

来源:

https://social.msdn.microsoft.com/Forums/en-US/5c9b4c31-850d-41c4-8ea3-fae734b348c4/copy-listsomeobject-to-clone-list?forum=csharpgeneral

于 2017-03-27T12:50:07.543 回答
9

你可以使用这个:

var newList= JsonConvert.DeserializeObject<List<Book>>(list.toJson());
于 2019-04-24T13:55:54.577 回答
7
List<Book> books_2 = new List<Book>(books_2.ToArray());

那应该完全符合您的要求。在这里演示。

于 2018-04-16T02:25:03.120 回答
4

C# 9记录with 表达式可以使它更容易一些,特别是如果你的类型有很多属性。

你可以使用类似的东西:

var books2 = books1.Select(b => b with { }).ToList();

我以此为例

record Book
{
    public string Name { get; set; }
}

static void Main()
{
    List<Book> books1 = new List<Book>()
    {
        new Book { Name = "Book1.1" },
        new Book { Name = "Book1.2" },
        new Book { Name = "Book1.3" }
    };

    var books2 = books1.Select(b => b with { }).ToList();

    books2[0].Name = "Changed";
    books2[1].Name = "Changed";

    Console.WriteLine("Books1 contains:");
    foreach (var item in books1)
    {
        Console.WriteLine(item);
    }

    Console.WriteLine("Books2 contains:");
    foreach (var item in books2)
    {
        Console.WriteLine(item);
    }
}

输出是:(对 Books2 中的对象所做的更改不会影响 Books1 中的原始对象)

Books1 包含:

书{名称=书1.1}

书{名称=书1.2}

书{名称=书1.3}

Books2 包含:

书{名称=已更改}

书{名称=已更改}

书{名称=书1.3}

于 2021-01-05T18:26:24.253 回答
2

由于Clone将返回 Book 的对象实例,因此首先需要将该对象强制转换为 Book,然后才能调用ToList它。上面的例子需要写成:

List<Book> books_2 = books_1.Select(book => (Book)book.Clone()).ToList();
于 2014-10-01T21:29:00.330 回答
2
public static class Cloner
{
    public static T Clone<T>(this T item)
    {
        FieldInfo[] fis = item.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
        object tempMyClass = Activator.CreateInstance(item.GetType());
        foreach (FieldInfo fi in fis)
        {
            if (fi.FieldType.Namespace != item.GetType().Namespace)
                fi.SetValue(tempMyClass, fi.GetValue(item));
            else
            {
                object obj = fi.GetValue(item);
                if (obj != null)
                    fi.SetValue(tempMyClass, obj.Clone());
            }
        }
        return (T)tempMyClass;
    }
}
于 2019-01-29T15:23:58.627 回答
0

如果 Array 类满足您的需求,您还可以使用 List.ToArray 方法,该方法将元素复制到新数组中。

参考: http: //msdn.microsoft.com/en-us/library/x303t819 (v=vs.110).aspx

于 2015-01-15T15:12:53.417 回答
-3

直接复制任何通用列表的简单方法:

List<whatever> originalCopy=new List<whatever>();//create new list
originalCopy.AddRange(original);//perform copy of original list
于 2020-02-01T09:44:32.420 回答