实例集合的意外行为
Unexpected behavior for a collection of an instance
例如我有以下 class:
class Person
{
public List<int> Grades{ get; set; }
public Person(List<int> grades)
{
this.Grades = grades;
}
}
然后我在主要方法中使用了这个 class,例如:
static void Main(string[] args)
{
List<int> grades = new List<int> { 1, 2, 3, 4, 5 };
Person person = new Person(grades);
for (int i = 0; i < 5; i++)
{
if (i % 2 == 0)
{
grades[i] = 0;
}
else
{
grades[i] = -1;
}
}
foreach(var grade in person.Grades)
{
Console.Write(grade + " ");
}
Console.ReadKey();
}
在 运行 之后,我希望控制台上有以下代码:
1 2 3 4 5
输出结果。
相反,我有这个输出结果:
0 -1 0 -1 0
我希望 Person 实例中的集合 "saved" 保持初始化状态。我应该怎么做才能使 Person 实例的这个集合保持不变,这样即使我在 main 方法中修改了成绩集合,我也会得到“1 2 3 4 5”输出结果。
谁能解释一下,为什么会这样?
this.Grades = grades;
上面的行只将 grades
持有的引用分配给 this.Grades
所以现在两者在内存中都是 pointing/referencing 相同的项目。
要解决它,因为它是 List<int>
,你可以
this.Grades = grades.ToList();
但是如果你有 List<T>
其中 T
是 class 或引用类型,你仍然会遇到同样的问题。参见:How create a new deep copy (clone) of a List<T>?
I expected that the collection "saved" into Person instance to stay how it was initialized.
是时候重新审视一下您对引用类型在 C# 中的工作方式的期望了 :) 我有一个 article 您可能会觉得有用...
What should i do to keep this collection unmodified for Person instances, so that i will have the "1 2 3 4 5" output result even if i modify the grades collection in main method.
您可以在构造函数中复制集合:
public Person(List<int> grades)
{
this.Grades = new List<int>(grades);
}
现在对于 List<int>
这很好 - 如果您有一个可变类型的列表,但是,您可能还想克隆每个 元素 。这种事情就是不可变类型很好的原因 - 更容易推理它们的行为,因为没有什么可以扰乱实例,所以你可以只保留引用...
局部变量 grades
和 Person
class 中的字段都引用同一个列表。将任一变量评估为它的值会导致返回相同的列表,因此改变一个存在的列表会创建可从任一变量观察到的变化。
如果在构造函数中分配 属性 的值时创建一个新列表并将其中的所有值复制到新列表,那么您将有两个单独的列表,并且改变一个不会'无法通过对方观察到。
列表是参考对象。
当你将列表传递给新的 person 对象时,你真正做的是传递一个 pointer 给列表,所以 person 和 local 中的列表列表变量 grades
实际上是同一个列表。
您可以让人员对象创建列表的副本,而不是使用以下任一方法:
this.Grades = grades.ToList();
// or
this.Grades = new List<int>(grades);
例如我有以下 class:
class Person
{
public List<int> Grades{ get; set; }
public Person(List<int> grades)
{
this.Grades = grades;
}
}
然后我在主要方法中使用了这个 class,例如:
static void Main(string[] args)
{
List<int> grades = new List<int> { 1, 2, 3, 4, 5 };
Person person = new Person(grades);
for (int i = 0; i < 5; i++)
{
if (i % 2 == 0)
{
grades[i] = 0;
}
else
{
grades[i] = -1;
}
}
foreach(var grade in person.Grades)
{
Console.Write(grade + " ");
}
Console.ReadKey();
}
在 运行 之后,我希望控制台上有以下代码: 1 2 3 4 5 输出结果。 相反,我有这个输出结果: 0 -1 0 -1 0
我希望 Person 实例中的集合 "saved" 保持初始化状态。我应该怎么做才能使 Person 实例的这个集合保持不变,这样即使我在 main 方法中修改了成绩集合,我也会得到“1 2 3 4 5”输出结果。
谁能解释一下,为什么会这样?
this.Grades = grades;
上面的行只将 grades
持有的引用分配给 this.Grades
所以现在两者在内存中都是 pointing/referencing 相同的项目。
要解决它,因为它是 List<int>
,你可以
this.Grades = grades.ToList();
但是如果你有 List<T>
其中 T
是 class 或引用类型,你仍然会遇到同样的问题。参见:How create a new deep copy (clone) of a List<T>?
I expected that the collection "saved" into Person instance to stay how it was initialized.
是时候重新审视一下您对引用类型在 C# 中的工作方式的期望了 :) 我有一个 article 您可能会觉得有用...
What should i do to keep this collection unmodified for Person instances, so that i will have the "1 2 3 4 5" output result even if i modify the grades collection in main method.
您可以在构造函数中复制集合:
public Person(List<int> grades)
{
this.Grades = new List<int>(grades);
}
现在对于 List<int>
这很好 - 如果您有一个可变类型的列表,但是,您可能还想克隆每个 元素 。这种事情就是不可变类型很好的原因 - 更容易推理它们的行为,因为没有什么可以扰乱实例,所以你可以只保留引用...
局部变量 grades
和 Person
class 中的字段都引用同一个列表。将任一变量评估为它的值会导致返回相同的列表,因此改变一个存在的列表会创建可从任一变量观察到的变化。
如果在构造函数中分配 属性 的值时创建一个新列表并将其中的所有值复制到新列表,那么您将有两个单独的列表,并且改变一个不会'无法通过对方观察到。
列表是参考对象。
当你将列表传递给新的 person 对象时,你真正做的是传递一个 pointer 给列表,所以 person 和 local 中的列表列表变量 grades
实际上是同一个列表。
您可以让人员对象创建列表的副本,而不是使用以下任一方法:
this.Grades = grades.ToList();
// or
this.Grades = new List<int>(grades);