如何分配 List<T>,而不是引用原始的 List<T>?

91
例如
List<string> name_list1 = new List<string>();
List<string> name_list2 = new List<string>();

在代码的后面:

name_list1.Add("McDonald");
name_list1.Add("Harveys");
name_list1.Add("Wendys");

name_list2 = name_list1; // I make a copy of namelist1 to namelist2

所以,从这个角度出发,我想在不影响name_list1的情况下继续向name_list2中添加元素或进行更改。我该怎么做?


这个解决方案适用于每个复杂对象的克隆。 - Kalpesh Dabhi
17个回答

189
name_list2 = new List<string>(name_list1);

这将克隆列表。

编辑:这个解决方案仅适用于原始类型。对于对象,请查看下面的其他回答。


2
这是一个不错、干净的解决方案。让我澄清一下,如果你这样做,就不必在几行代码之前使用 new List<string>() 构造函数初始化 name_list2,因为你将创建一个对象只是为了扔掉它。虽然不是什么大问题,但这是一个不好的编码习惯。 - adv12
69
这仍然涉及到name_list1。 - DoIt
35
这只适用于基本数据类型,比如字符串列表。如果你有一个复杂对象,它将不起作用,你需要使用深度克隆或者创建另一个与第一个相同的列表。 - Joshua Duxbury
9
确实,这只适用于原始类型,应该添加到问题中。 - Zimano
1
如果您在name_list2中编辑了任何已经存在于name_list1中的元素,则会反映在两个列表上的更改。这不是准确的解决方案。 - VectorX
显示剩余5条评论

14
另一个选择是:深度克隆。
public static T DeepCopy<T>(T item)
        {
            BinaryFormatter formatter = new BinaryFormatter();
            MemoryStream stream = new MemoryStream();
            formatter.Serialize(stream, item);
            stream.Seek(0, SeekOrigin.Begin);
            T result = (T)formatter.Deserialize(stream);
            stream.Close();
            return result;
        }

所以,您可以使用以下内容:

你可以使用:

name_list2 = DeepCopy<List<string>>(name_list1);

或者:

name_list2 = DeepCopy(name_list1); 

也适用。


4
不错的解决方案,但请记住,如果这是一个引用类型,你的类始终需要带有 Serializable 属性。 - Jordy van Eijk
是的,你说得对。这是在进行深度克隆时需要牢记的最重要的一点。 - aru
2
如果改为 Clone<T>(this T item),那么调用起来会更加方便,比如可以这样写:name_list2=name_list1.Clone()。在我看来,这样会更好一些。 - Ole Albers
在定义 DeepCopy 的地方,你需要添加 using System.Runtime.Serialization.Formatters.Binary; 以使用 BinaryFormatter,并且需要添加 using System.IO; 以使用 MemoryStream - Paul Masri-Stone

11

对于原始类型,您可以这样做:

List<string> CopyList = new List<string>(OriginalList);

对于非原始类型或用户自定义类型,您可以这样操作:

List<Person> CopyList = new List<Person>();
foreach(var item in OriginalList)
{
    CopyList.Add(new Person { 
            Name = item.Name,
            Address = item.Address
    });
}

5
name_list2 = new List<string>(name_list1); // Clone list into a different object

此时,这两个列表是不同的对象。您可以向list2中添加项目,而不会影响list1。


9
“你可以向列表2中添加项目,而不会影响列表1。” 这句话是错误的。 - Sponsor
3
那对我没用,仍涉及到了之前的内容。我使用了这个链接里的方法 https://dev59.com/e4Lba4cB1Zd3GeqPYgK_#25035202 - Sabri Meviş
5
您可以在不影响list1的情况下向list2中添加和删除内容,但是当您更新一个项目时,它会在两个列表中同时传播。 - Arh Hokagi

5

我喜欢用linq来做这件事...

如果列表元素是基本类型或结构体,则...

L2 = L1.ToList()

如果列表元素是类,则...
L2 = L1.Select(x => x.Copy()).ToList();

Copy可以是MemberWiseClone的浅复制,也可以是深复制的某种实现。


对我来说,L1.Select(x => x.Clone()).ToList(); 可以工作。 - Nilay Mehta
1
什么是 .Clone()? - CDrosos
@CDrosos 这段时间我已经很久没有使用C#了,但我的猜测是它可能是ICloneable的类依赖实现(https://learn.microsoft.com/en-us/dotnet/api/system.icloneable.clone?view=netframework-4.8)。 - u8it
你不需要实现ICloneable接口,使用LINQ的MemberWiseClone方法,你只需要在想要复制的类中创建一个Clone()方法即可(https://dev59.com/wV7Va4cB1Zd3GeqPOOUs)。 - Matthew

4
问题出在赋值语句上。在执行 name_list2 = name_list1; 这条语句之前,变量 name_list1name_list2 分别指向堆中两个不同的 List 对象。你往 name_list1 中添加了元素,这是没问题的。但是赋值语句的意思是“让 name_list2 指向与 name_list1 相同的堆对象”。此时原来被 name_list2 指向的 List 对象就无法访问了,并会被垃圾回收。实际上你需要的是将 name_list1 的内容复制到 name_list2 中。可以使用 List.AddRange 方法来实现。需要注意的是,这样做只能进行“浅拷贝”,对于本例中元素为字符串的情况是可行的,但如果列表成员是更复杂的对象,则可能不适用。具体还要根据需求而定。

这个答案比我之前读到的其他答案更详细地解释了正在发生的事情,但我同意其他人提出的name_list2 = new List<string>(name_list1);解决方案可能是实际进行复制的更干净的方式。 - adv12

4

根据@Mrunal的答案,我创建了一个扩展方法:

public static T Clone<T>(this T source)
    {
        // Don't serialize a null object, simply return the default for that object
        if (source == null)
        {
            return default;
        }

        // initialize inner objects individually
        // for example in default constructor some list property initialized with some values,
        // but in 'source' these items are cleaned -
        // without ObjectCreationHandling.Replace default constructor values will be added to result
        var deserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };

        return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
    }

你可以这样调用:

L2 = L1.Select(x => x.Clone()).ToList();

3
这里有一个替代方案:
    List<string> name_list1 = new List<string>();
    List<string> name_list2 = new List<string>();

    name_list1.Add("McDonald");
    name_list1.Add("Harveys");
    name_list1.Add("Wendys");

    name_list2.AddRange(name_list1.ToArray());

ToArray()方法将'name_list1'复制到一个新数组中,然后我们通过AddRange()方法将其添加到name_list2中。


我认为它仅适用于原始类型。 - Suchith

3

1

你可以通过序列化和反序列化来克隆复杂对象,这将消除你的对象引用并创建一个新的没有引用的对象。

using Newstonsoft.Json;

List<string> name_list1 = new List<string>();
name_list1.Add("McDonald");
name_list1.Add("Harveys");
name_list1.Add("Wendys");

name_list2 = name_list1; 

List<string> name_list2 = JsonConvert.DeserializeObject<List<string>> 
(JsonConvert.SerializeObject(name_list1)); // Ii make a copy of namelist1 to namelist2

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