我在使用C#时遇到问题,如果我初始化某个列表,让我们使用另一个预先存在的列表说List<T> exampleList
,让我们说 toModify :List<T> exampleList = new List<T>(toModify)
。当我稍后修改 toModify 列表时,新创建的列表也会修改自己。如果它通过引用传递值不应该 exampleList 的值保持不变,因为它是从另一个生成的?
TLDR:当我更改第二个列表时,我使用另一个列表(第二个列表)初始化的列表的值会发生变化。我来自Java背景,无法理解为什么会发生这种情况。我总是要使用克隆吗?
答案 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