219

Let's say I have a class

public class MyObject
{
   public int SimpleInt{get;set;}
}

And I have a List<MyObject>, and I ToList() it and then change one of the SimpleInt, will my change be propagated back to the original list. In other words, what would be the output of the following method?

public void RunChangeList()
{
  var objs = new List<MyObject>(){new MyObject(){SimpleInt=0}};
  var whatInt = ChangeToList(objs );
}
public int ChangeToList(List<MyObject> objects)
{
  var objectList = objects.ToList();
  objectList[0].SimpleInt=5;
  return objects[0].SimpleInt;

}

Why?

P/S: I'm sorry if it seems obvious to find out. But I don't have compiler with me now...

Graviton
  • 81,782
  • 146
  • 424
  • 602
  • 5
    One way of phrasing it, is that `.ToList()` makes a ___shallow___ copy. The references are copied, but the new references still point to the same instances as the original references point to. When you think of it, `ToList` cannot create any `new MyObject()` when `MyObject` is a `class` type. – Jeppe Stig Nielsen Jul 10 '19 at 11:57

12 Answers12

269

Yes, ToList will create a new list, but because in this case MyObject is a reference type then the new list will contain references to the same objects as the original list.

Updating the SimpleInt property of an object referenced in the new list will also affect the equivalent object in the original list.

(If MyObject was declared as a struct rather than a class then the new list would contain copies of the elements in the original list, and updating a property of an element in the new list would not affect the equivalent element in the original list.)

LukeH
  • 263,068
  • 57
  • 365
  • 409
  • 3
    Also note that with a `List` of structs, an assignment like `objectList[0].SimpleInt=5` would not be allowed (C# compile-time error). That is because the return value of the list indexer's `get` accessor is not a variable (it is a returned copy of a struct value), and therefore _setting_ its member `.SimpleInt` with an assignment expression is not allowed (it would mutate a copy that is not kept). Well, who uses mutable structs anyway? – Jeppe Stig Nielsen Jul 06 '16 at 16:31
  • 5
    @Jeppe Stig Nielson: Yes. And, similarly, the compiler will also stop you doing stuff like `foreach (var s in listOfStructs) { s.SimpleInt = 42; }`. The _really_ nasty gotcha is when you attempt something like `listOfStructs.ForEach(s => s.SimpleInt = 42)`: the compiler allows it and the code runs without exceptions, but the structs in the list will stay unchanged! – LukeH Jul 06 '16 at 17:03
  • 3
    Another note: clearing the original List will not delete its elements from memory (the references in the new List will still point to the values referenced at the time ToList was called). – ryanwebjackson Aug 11 '20 at 01:26
66

From the Reflector'd source:

public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    return new List<TSource>(source);
}

So yes, your original list won't be updated (i.e. additions or removals) however the referenced objects will.

Chris S
  • 64,770
  • 52
  • 221
  • 239
42

ToList will always create a new list, which will not reflect any subsequent changes to the collection.

However, it will reflect changes to the objects themselves (Unless they're mutable structs).

In other words, if you replace an object in the original list with a different object, the ToList will still contain the first object.
However, if you modify one of the objects in the original list, the ToList will still contain the same (modified) object.

jps
  • 20,041
  • 15
  • 75
  • 79
SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
13

Yes, it creates a new list. This is by design.

The list will contain the same results as the original enumerable sequence, but materialized into a persistent (in-memory) collection. This allows you to consume the results multiple times without incurring the cost of recomputing the sequence.

The beauty of LINQ sequences is that they are composable. Often, the IEnumerable<T> you get is the result of combining multiple filtering, ordering, and/or projection operations. Extension methods like ToList() and ToArray() allow you to convert the computed sequence into a standard collection.

Community
  • 1
  • 1
LBushkin
  • 129,300
  • 32
  • 216
  • 265
12

The accepted answer correctly addresses the OP's question based on his example. However, it only applies when ToList is applied to a concrete collection; it does not hold when the elements of the source sequence have yet to be instantiated (due to deferred execution). In case of the latter, you might get a new set of items each time you call ToList (or enumerate the sequence).

Here is an adaptation of the OP's code to demonstrate this behaviour:

public static void RunChangeList()
{
    var objs = Enumerable.Range(0, 10).Select(_ => new MyObject() { SimpleInt = 0 });
    var whatInt = ChangeToList(objs);   // whatInt gets 0
}

public static int ChangeToList(IEnumerable<MyObject> objects)
{
    var objectList = objects.ToList();
    objectList.First().SimpleInt = 5;
    return objects.First().SimpleInt;
}

Whilst the above code may appear contrived, this behaviour can appear as a subtle bug in other scenarios. See my other example for a situation where it causes tasks to get spawned repeatedly.

InteXX
  • 6,135
  • 6
  • 43
  • 80
Douglas
  • 53,759
  • 13
  • 140
  • 188
7

Just stumble upon this old post and thought of adding my two cents. Generally, if I am in doubt, I quickly use the GetHashCode() method on any object to check the identities. So for above -

    public class MyObject
{
    public int SimpleInt { get; set; }
}


class Program
{

    public static void RunChangeList()
    {
        var objs = new List<MyObject>() { new MyObject() { SimpleInt = 0 } };
        Console.WriteLine("objs: {0}", objs.GetHashCode());
        Console.WriteLine("objs[0]: {0}", objs[0].GetHashCode());
        var whatInt = ChangeToList(objs);
        Console.WriteLine("whatInt: {0}", whatInt.GetHashCode());
    }

    public static int ChangeToList(List<MyObject> objects)
    {
        Console.WriteLine("objects: {0}", objects.GetHashCode());
        Console.WriteLine("objects[0]: {0}", objects[0].GetHashCode());
        var objectList = objects.ToList();
        Console.WriteLine("objectList: {0}", objectList.GetHashCode());
        Console.WriteLine("objectList[0]: {0}", objectList[0].GetHashCode());
        objectList[0].SimpleInt = 5;
        return objects[0].SimpleInt;

    }

    private static void Main(string[] args)
    {
        RunChangeList();
        Console.ReadLine();
    }

And answer on my machine -

  • objs: 45653674
  • objs[0]: 41149443
  • objects: 45653674
  • objects[0]: 41149443
  • objectList: 39785641
  • objectList[0]: 41149443
  • whatInt: 5

So essentially the object that list carries remain the same in above code. Hope the approach helps.

vibhu
  • 1,657
  • 2
  • 15
  • 19
7

A new list is created but the items in it are references to the orginal items (just like in the original list). Changes to the list itself are independent, but to the items will find the change in both lists.

Preet Sangha
  • 64,563
  • 18
  • 145
  • 216
4

I think that this is equivalent to asking if ToList does a deep or shallow copy. As ToList has no way to clone MyObject, it must do a shallow copy, so the created list contains the same references as the original one, so the code returns 5.

Timores
  • 14,439
  • 3
  • 46
  • 46
2

In the case where the source object is a true IEnumerable (i.e. not just a collection packaged an as enumerable), ToList() may NOT return the same object references as in the original IEnumerable. It will return a new List of objects, but those objects may not be the same or even Equal to the objects yielded by the IEnumerable when it is enumerated again

prmph
  • 7,616
  • 11
  • 37
  • 46
2

ToList will create a brand new list.

If the items in the list are value types, they will be directly updated, if they are reference types, any changes will be reflected back in the referenced objects.

Oded
  • 489,969
  • 99
  • 883
  • 1,009
1
 var objectList = objects.ToList();
  objectList[0].SimpleInt=5;

This will update the original object as well. The new list will contain references to the objects contained within it, just like the original list. You can change the elements either and the update will be reflected in the other.

Now if you update a list (adding or deleting an item) that will not be reflected in the other list.

kemiller2002
  • 113,795
  • 27
  • 197
  • 251
0

I don't see anywhere in the documentation that ToList() is always guaranteed to return a new list. If an IEnumerable is a List, it may be more efficient to check for this and simply return the same List.

The worry is that sometimes you may want to be absolutely sure that the returned List is != to the original List. Because Microsoft doesn't document that ToList will return a new List, we can't be sure (unless someone found that documentation). It could also change in the future, even if it works now.

new List(IEnumerable enumerablestuff) is guaranteed to return a new List. I would use this instead.

Ben B.
  • 3,706
  • 5
  • 26
  • 27
  • 2
    It says it in the documentation here: "[The ToList(IEnumerable) method forces immediate query evaluation and returns a List that contains the query results. You can append this method to your query in order to obtain a cached copy of the query results.](http://msdn.microsoft.com/en-us/library/bb342261.aspx)" The second sentence reiterates that any changes to the original list after the query is evaluated has no effect on the returned list. – Raymond Chen Aug 26 '13 at 04:42
  • 1
    @RaymondChen This is true for IEnumerables, but not for Lists. Although `ToList` seems to create a new list object reference when called on a `List`, BenB is right when he say that this is not guaranteed by MS documentation. – Teejay Apr 07 '16 at 08:58
  • @RaymondChen As per ChrisS source, `IEnumerable<>.ToList()` is actually implemented as `new List<>(source)` and there is no specific override for `List<>`, so `List<>.ToList()` does indeed return a new list object reference. But once again, as per MS documentation, there is no guarantee for this not to change in the future, although it will likely break most of the code out there. – Teejay Apr 07 '16 at 09:14
  • @Teejay Yes there is a guarantee - as per @RaymondChen's quote. Saying "not for Lists" makes no sense - since the quote @RaymondChen applies to all callers to `IEnumerable.ToList` - _including_ lists. – mjwills Oct 10 '22 at 04:59