-2

I have a list of apples, and I find those which are red:

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

redApples is an IEnumerable here, so if I convert this to a list:

var redApplesList = redApples.ToList();

This gives me a list of new objects. So if I modify these objects:

redApplesList.ForEach(a => a.color = "blue");

I would not expect the items in my original apples list to change colour at all. But this isn't what happens, the apples in my original list which were "red" are now "blue".

What is the misunderstanding here?

I was under the impression ToList() created a completely new list of items independent from existing lists, but this suggests the pointers in the original list were updated to point to the newly created objects? Is this what's happening under the hood?

A lot of developers seem to resort to separating things out into separate lists (or even cloning objects) because they're never 100% sure what they're working with. It would be helpful to have a confident understanding of this area.

Gilad Green
  • 36,708
  • 7
  • 61
  • 95
FBryant87
  • 4,273
  • 2
  • 44
  • 72
  • 4
    Because the `apple` type is a reference type, then if you make a copy of it, you have copied the reference to the `apple` object, NOT the contents of the object itself. Therefore when you change `a.color` you are changing the original object via a copied reference to it. – Matthew Watson Nov 01 '16 at 13:47
  • 1
    *'This gives me a list of new objects'* - no, it doesn't. It gives you a new list of the *same* objects. – Charles Mager Nov 01 '16 at 13:47
  • Read up on the difference between a "shallow copy" and a "deep copy". Also, understand that your list only holds a reference to the instances in it - so when you clone your list, both lists are still pointing to the same instances of the objects within them. – RB. Nov 01 '16 at 13:48
  • 2
    Knowing the difference between value/reference types doesn't answer the above - the (incorrect) assumption was that .ToList() created new reference types. – FBryant87 Nov 01 '16 at 13:52
  • 5
    @FBryant87 - Actually, it does. The key to your issue is understanding how value types and reference types exist and behave differently. – Wesley Long Nov 01 '16 at 14:00
  • @WesleyLong The key issue is the false assumption that .ToList() creates deep copies, thus I don't agree the suggested duplicate was a clear answer to the question. – FBryant87 Nov 01 '16 at 14:49

2 Answers2

6

It gives you a new list, but that list contains references (apple is a class = reference type) to the same objects like the original list. They are not copied so when you reference one of them via the second list and change it, it is the same original item that is updated.

If you are looking to copy the items look into deep copy and one way is by using ICloneable (another way as Kyle commented is using a copy constructor)

If you implement you class like this:

public class Apple : ICloneable
{
    public string Color { get; set; }

    public object Clone()
    {
        return new Apple { Color = Color };
    }
}

Then check this:

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

Without the call to .Clone as in your you get the same references. With the .Clone you get new objects. Thus, when you change their Color it does not effect the original


After reading a bit more (Copy constructor versus Clone()) I'd suggest go for copy constructor instead:

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();
Community
  • 1
  • 1
Gilad Green
  • 36,708
  • 7
  • 61
  • 95
  • 1
    `ICloneable` does not guarantee that the copy it produces is a deep copy. It also doesn't force you to create a deep copy (in fact [Microsoft recommends](https://msdn.microsoft.com/en-us/library/system.icloneable(v=vs.110).aspx) that you don't publically expose `ICloneable` in a API due to this ambiguity). I'd rather implement a copy constructor for the `Apple` class. – Kyle Nov 01 '16 at 14:10
  • @Kyle - this is a good comment. I didn't say that every `ICloneable` is necessarily a deep copy but the one in the snippet I gave is. – Gilad Green Nov 01 '16 at 14:13
3

It doesn't create a list of new objects. It creates a new list of the existing objects.