实例集合的意外行为

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> 这很好 - 如果您有一个可变类型的列表,但是,您可能还想克隆每个 元素 。这种事情就是不可变类型很好的原因 - 更容易推理它们的行为,因为没有什么可以扰乱实例,所以你可以只保留引用...

局部变量 gradesPerson class 中的字段都引用同一个列表。将任一变量评估为它的值会导致返回相同的列表,因此改变一个存在的列表会创建可从任一变量观察到的变化。

如果在构造函数中分配 属性 的值时创建一个新列表并将其中的所有值复制到新列表,那么您将有两个单独的列表,并且改变一个不会'无法通过对方观察到。

列表是参考对象。

当你将列表传递给新的 person 对象时,你真正做的是传递一个 pointer 给列表,所以 person 和 local 中的列表列表变量 grades 实际上是同一个列表。

您可以让人员对象创建列表的副本,而不是使用以下任一方法:

this.Grades = grades.ToList();
// or
this.Grades = new List<int>(grades);