在C#中生成List的新实例

时间:2016-08-01 18:07:49

标签: c# list reference clone

我在使用C#时遇到问题,如果我初始化某个列表,让我们使用另一个预先存在的列表说List<T> exampleList,让我们说 toModify List<T> exampleList = new List<T>(toModify) 。当我稍后修改 toModify 列表时,新创建的列表也会修改自己。如果它通过引用传递值不应该 exampleList 的值保持不变,因为它是从另一个生成的?

TLDR:当我更改第二个列表时,我使用另一个列表(第二个列表)初始化的列表的值会发生变化。我来自Java背景,无法理解为什么会发生这种情况。我总是要使用克隆吗?

3 个答案:

答案 0 :(得分:3)

您正在创建一个包含与旧列表相同的项目的新列表。如果清除第一个列表,则第二个列表中的项目会停止。

但是如果你为第一个列表中的一个项目更改了一个属性,那么它就是第二个列表中的同一个对象。

因此,两个列表都引用了内存中的相同项。当您编写list1[0].SomeProperty = 1时,您正在使用list2中相同的对象引用来更改它,因此更改会反映在第二个列表中。

有关如何克隆List并为项生成新引用,请检查此SO Answer

答案 1 :(得分:2)

在以下行中:

List<T> exampleList = new List<T>(toModify)

您创建一个T调用List<T>的构造函数列表,该构造函数接受一个IEnumerable<T>类型的参数。有关后者的更多信息,请查看here

C#中的方法参数默认按 传递而不是按引用传递。它们可以通过引用传递,但是您必须使用ref关键字在相应方法的签名中明确说明这一点,并且在您调用此方法时,再次使用相同的关键字。因此,toModify按值传递给List<T>的构造函数。

这有什么重要性?

在C#类型中可以分为两类(尽管所有类型都继承自System.Object):

  • 值类型
  • 参考类型

当我们将值类型作为参数传递时,我们传递它的值的副本。我们在原始值或原始值的副本中进行的每个修改都不会相互反映。另一方面,当我们传递引用类型作为参数时,我们传递该引用的副本。所以现在我们有两个指向内存中相同位置的引用(指针)。话虽如此,很明显,如果我们改变两个引用指向的对象的任何属性,这两个属性都可以看到。

在你的情况下,这就是正在发生的事情。 toModify是一个引用类型列表(在引擎盖下有一个数组,其项目是对其他对象的引用)。因此,对初始列表toModify的项目的任何更改都会反映到您根据此列表构建的列表中。

您可以用来验证以上内容的简单示例如下:

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }

    public override string ToString() => $"X: {X}, Y: {Y}";
}

class Program
{
    static void Main(string[] args)
    {
        var listA = new List<int> {1, 2, 3};
        var listB = new List<int>(listA);
        // Before the modification
        Console.WriteLine(listA[0]); // prints 1
        Console.WriteLine(listB[0]); // prints 1
        listA[0] = 2;
        // After the mofication
        Console.WriteLine(listA[0]); // prints 2
        Console.WriteLine(listB[0]); // prints 1
        Console.ReadKey();

        var pointsA = new List<Point>
        {
            new Point {X = 3, Y = 4},
            new Point {X = 4, Y = 5},
            new Point {X = 6, Y = 8},
        };
        var pointsB = new List<Point>(pointsA);
        // Before the modification
        Console.WriteLine(pointsA[0]); // prints X: 3, Y: 4
        Console.WriteLine(pointsB[0]); // prints X: 3, Y: 4
        pointsA[0].X = 4; 
        pointsA[0].Y = 3;
        // After the modification
        Console.WriteLine(pointsA[0]); // prints X: 4, Y: 3
        Console.WriteLine(pointsB[0]); // prints X: 4, Y: 3

        Console.ReadKey();
    }
}

答案 2 :(得分:1)

让我们举个例子:

        List<A> firstList = new List<A>()
        {
            new A() { Id = 3 },
            new A() { Id = 5 }
        };

        List<A> secondList = new List<A>(firstList);

        secondList[1].Id = 999;

        Console.WriteLine(firstList[1].Id);

输出:999

这样做的主要原因是,即使我们创建了新的List<T>对象(yes集合也是对象),它指向在托管堆中分配的新内存,但它仍然适用于对相同对象的引用。

要创建具有相同值的新(!)对象列表,您需要以某种方式克隆元素,一种方法是使用LINQ .Select()方法创建新对象,使用ToList()方法将其转换为列表:

        List<A> firstList = new List<A>()
        {
            new A() { Id = 3 },
            new A() { Id = 5 }
        };

        List<A> secondList = firstList.Select(el => new A() { Id = el.Id }).ToList();

        secondList[1].Id = 999;

        Console.WriteLine(firstList[1].Id);

输出:5