提问人:Tomi Tsolov 提问时间:9/18/2023 更新时间:9/19/2023 访问量:108
为什么此方法不生成列表的深层副本?[复制]
Why doesn't this method produce a deep copy of the List? [duplicate]
问:
在此线程中:
如何创建 List<T> 的新深拷贝(克隆)?
提问者的例子是如何创建其列表的深层副本。我没有看到任何人提供关于为什么它不起作用的见解。为什么新列表表现为对旧列表的引用?(或者至少是元素)。
我了解引用在 C++ 中的工作方式,并且我认为它们在 C# 中的行为方式是相似的。但也许我错过了什么。我还是 C# 的新手。
下面是您可以粘贴和运行的简化版本:
namespace OOPtest1
{
internal class Program
{
public class Book
{
public string title = "";
public Book(string title)
{
this.title = title;
}
}
static void Main(string[] args)
{
//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"; // this changes the books_1 element as well
books_2[1].title = "Six"; // this changes the books_1 element as well
}
}
}
我以为这会表现得像一个完全不同的集合。
答:
首先,请注意 这是一个引用类型,因此对该类型实例的引用存储在 和 中。Book
books_1
books_2
当您复制 to 的内容时,您复制了对对象的引用,而不是对象本身的内容。books_1
books_2
Book
因此,在复制之后,每个列表的第一个元素的情况如下:
books_1[0]----\
\
{Book0}
/
books_2[0]----/
现在你应该能够看到,当你改变时,你将改变 - 并且因为那个对象被引用,所以它也会为该引用而改变它 - 因为它引用的是同一个对象。books_1[0].title
Book0.title
books_2[0]
如果您认为引用更像是 C++ 指针,您应该会得到它。
评论
5
i
由于这里似乎对 C++ 有一些熟悉,我想开始谈论 vs 类型。这是一个漫长的时间,但 IIRC 在 C++ 中 a 和 之间的主要区别是访问器默认值 ( vs )。在 C# 中,两者的默认值都是 ,它更接近于 ,但 (值类型) 与 (引用类型) 在语义上存在差异。强烈建议尽可能多地使用类,并保留小且不可变的类型。struct
class
struct
class
public
private
internal
private
struct
class
struct
我从这里开始,因为它将有助于理解接下来的一些内容,并且因为您可以通过将类型定义为 而不是 .但同样:不建议这样做。Book
struct
class
更进一步,并且知道这是一个引用类型,C# 不会假设复制引用类型对象的内存数据就足以深度复制该对象。想想开放流、文件句柄、com/串行端口、数据库连接、对其他内存/对象的引用、需要唯一 guid 成员的项目等。如果需要能够进行深度复制,则必须提供代码才能自己执行此操作,并且没有对等效于 C++ 复制构造函数的语言支持。Book
因此,当基于 创建列表时,虽然它确实复制了 中的内部集合的副本,但只复制引用。中的每个项目最初都与 中的等效项目引用相同的对象。books_2
books_1
books_1
books_2
books_1
我说“最初”,因为您当然可以在列表中添加和删除整个项目而不会影响.这样,您就拥有了初始列表的副本;列表是它自己的对象,而不仅仅是对与 相同的列表的引用。但是,由于该列表是针对引用类型的,因此您只有引用的副本,并且基础对象是共享的。books_2
books_1
books_2
books_1
因此,在原始代码中,如果要更改 和 的项,可以这样做:books_2[0]
books_2[1]
books_2[0] = new Book("Five");
books_2[1] = new Book("Six");
这将完全保持不变。但是,和 索引仍将在两个列表之间共享相同的对象。books_1
[2]
[3]
如果你真的想完全深度复制整个列表,你可以这样做:
var books_2 = books_1.Select(b => new Book(b.title)).ToList();
评论
books_1
实际上是对实例的引用的集合,因此构造函数(请参阅源代码)只会将它们复制到新位置。Book
List