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

时间:2016-11-01 13:45:48

标签: c#

我有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%确定他们正在使用什么。了解这一领域将是有益的。

2 个答案:

答案 0 :(得分:6)

它为您提供了一个新列表,但该列表包含与原始列表相同的对象的引用(apple是一个类=引用类型)。它们不会被复制,因此当您通过第二个列表引用其中一个并更改它时,它就是更新的原始项目。

如果您要复制项目,请查看deep copy,一种方法是使用ICloneable(另一种方式,因为Kyle评论使用的是copy constructor

如果你实现了这样的课程:

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时,它不会影响原始

在阅读了更多内容(Copy constructor versus 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();

答案 1 :(得分:3)

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