当在IEnumerable上调用. tolist()时,它选择了现有列表的一部分,为什么在修改新列表时更新原始列表?

本文关键字:列表 为什么 一部分 修改 新列表 更新 原始 调用 IEnumerable tolist 当在 | 更新日期: 2023-09-27 18:15:58

我有一个apples的列表,我找到了那些红色的:

var redApples = apples.Where(a => a.colour == "red");

redApples在这里是一个IEnumerable,所以如果我把它转换成一个列表:

var redApplesList = redApples.ToList();
这给了我一个新对象的列表。如果我修改这些对象
redApplesList.ForEach(a => a.color = "blue");

我不希望我原来的apples列表中的项目完全改变颜色。但事实并非如此,我原来列表中"red"的苹果现在变成了"blue"

这里的误解是什么?

我的印象是ToList()创建了一个独立于现有列表的全新列表,但这表明原始列表中的指针被更新为指向新创建的对象?这就是引擎盖下发生的事吗?

许多开发人员似乎求助于将东西分离到单独的列表(甚至克隆对象),因为他们从来没有100%确定他们正在处理什么。对这个领域有一个自信的了解是很有帮助的。

当在IEnumerable上调用. tolist()时,它选择了现有列表的一部分,为什么在修改新列表时更新原始列表?

它给出了一个新的列表,但是该列表包含对与原始列表相同对象的引用(apple是一个class=引用类型)。它们没有被复制,所以当你通过第二个列表引用其中一个并改变它时,它是被更新的相同的原始项。

如果你想复制项目,查看深度复制,一种方法是使用ICloneable(另一种方法是使用复制构造函数)

如果你像这样实现你的类:

public class Apple : ICloneable
{
    public string Color { get; set; }
    public object Clone()
    {
        return new Apple { Color = Color };
    }
}

然后检查:

List<Apple> apples = new List<Apple>
{
     new Apple { Color = "red" },
     new Apple { Color = "blue" },
};
var redAppels = apples.Where(a => a.Color == "red").Select(a => (Apple)a.Clone()).ToList();
redAppels[0].Color = "green";
Console.WriteLine($"Original: {apples[0].Color}, new: {redAppels[0].Color}");
// Original red, new: green

如果没有调用.Clone,您将获得相同的引用。使用.Clone,您可以获得新对象。因此,当您更改它们的Color时,它不会影响原来的


在阅读了更多(复制构造函数与Clone())之后,我建议使用复制构造函数:

public class Apple
{
    public Apple() { }
    public Apple(Apple apple)
    {
        Color = apple?.Color;
    }
    public string Color { get; set; }
}
var redAppels = apples.Where(a => a.Color == "red")
                      .Select(a => new Apple(a))
                      .ToList();

它不创建新对象的列表。它创建现有对象新列表