0

I have a problem with using C#, if I initialize a certain list, lets say List<T> exampleList using another pre-existing list, lets say toModify like this: List<T> exampleList = new List<T>(toModify). When I later modify toModify list the newly created list also modifies itself. If it passes the value by reference shouldn't the value of exampleList stay the same since it was generated from the other one?

TLDR: Value of a list I initialize using another list(second list) changes when I change the second list. I come from a Java background and can't understand why this happens. Will I always have to use clone?

Daniel Rodríguez
  • 684
  • 3
  • 7
  • 15
  • 5
    C#'s behavior is exactly the same as Java's behavior in this regard. Can you explain what you mean when you say you "modify" the list? Perhaps provide a minimal code example to show the behavior you're seeing? – StriplingWarrior Aug 01 '16 at 18:11
  • [C# Concepts: Value vs Reference Types](http://www.albahari.com/valuevsreftypes.aspx) – Reza Aghaei Aug 02 '16 at 00:37

3 Answers3

8

Let us use this example :

        List<A> firstList = new List<A>()
        {
            new A() { Id = 3 },
            new A() { Id = 5 }
        };

        List<A> secondList = new List<A>(firstList);

        secondList[1].Id = 999;

        Console.WriteLine(firstList[1].Id);

Output : 999

The main reason for this is that even though we created a new List<T> that points to a new memory allocated on heap it still works with references the point to same objects.

To create a list that points to new (!) objects with the same values we'd need to clone these elements somehow, one way to do it is to use LINQ .Select() method in order to create new objects and then a ToList() method to copy the list itself:

        List<A> firstList = new List<A>()
        {
            new A() { Id = 3 },
            new A() { Id = 5 }
        };

        List<A> secondList = firstList.Select(el => new A() { Id = el.Id }).ToList();

        secondList[1].Id = 999;

        Console.WriteLine(firstList[1].Id);

Output : 5

Fabjan
  • 13,506
  • 4
  • 25
  • 52
3

Yes.

You're creating a new list containing the same items as the old list. If you clear the first list, the items in the second stay.

But if you change a property for one of the items in the first list, then it is the same object in the second list.

So, both list are referencing the same items in memory. When you write list1[0].SomeProperty = 1 you're changing that using object reference that is the same in list2, so changes are reflected in the second list.

For how to clone a List and generate new references for items, check this SO Answer.

Community
  • 1
  • 1
Zein Makki
  • 29,485
  • 6
  • 52
  • 63
  • Why why dont they make a function for the interface List so that you can copy it without that happening? In other words change the memory reference creating a new one without having to create a clone method to do the job? – Daniel Rodríguez Aug 01 '16 at 18:13
  • @TiPiTo Copying an object is more complex that you imagine. You may have a hierarchy of sub objects and complex properties. Google "Deep Copy" in C#. – Zein Makki Aug 01 '16 at 18:16
  • Wouldn't using a deep copy be a very inefficient way to "clone" the object, since it goes through every index instead of just copying the memory block? – Daniel Rodríguez Aug 01 '16 at 18:22
  • @TiPiTo Yes, which is why it's a red flag to see it being done; typically you'd design the code in such a way that it's not necessary. – Servy Aug 01 '16 at 18:31
  • Now that you mention that, what can I do in order to prevent having to deal with this? Is there a pattern or something else that is usually implemented in cases where this problem is expected? – Daniel Rodríguez Aug 01 '16 at 18:34
2

In the following line:

List<T> exampleList = new List<T>(toModify)

you create a list of T calling List<T>'s constructor that takes one argument of type IEnumerable<T>. For further info on the latter, please have a look here.

Method's arguments in C# are passed by default by value and not by reference. They can be passed by reference, but you have to explicitly state this in the signature of the corresponding method using the ref keyword and at the point you call this method, using again the same keyword. So the toModify is passed by value to the constructor of List<T>.

What's the importance of this?

In C# types can be divided into two categories (despite the fact that all types inherit from the System.Object):

  • Value types
  • Reference types

When we pass a value type as an argument, we pass a copy of it's value. Each modification we make in either the original value or in the copy of the original value is not reflected to one another. On the other hand, when we pass a reference type as an argument, we pass a copy of that reference. So now we have two references (pointers) that point to the same location in memory. That being said, it's clear that if we change any property of the object in which both references points to, this would be visible by both of them.

In your case, this is what is happening. toModify is a list of reference types (under the hood you have an array, whose items are references to other objects). So any change to the items of the initial list, toModify, is reflected to the list you construct based on this list.

A simple example that you could use to verify the above is the following:

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }

    public override string ToString() => $"X: {X}, Y: {Y}";
}

class Program
{
    static void Main(string[] args)
    {
        var listA = new List<int> {1, 2, 3};
        var listB = new List<int>(listA);
        // Before the modification
        Console.WriteLine(listA[0]); // prints 1
        Console.WriteLine(listB[0]); // prints 1
        listA[0] = 2;
        // After the mofication
        Console.WriteLine(listA[0]); // prints 2
        Console.WriteLine(listB[0]); // prints 1
        Console.ReadKey();

        var pointsA = new List<Point>
        {
            new Point {X = 3, Y = 4},
            new Point {X = 4, Y = 5},
            new Point {X = 6, Y = 8},
        };
        var pointsB = new List<Point>(pointsA);
        // Before the modification
        Console.WriteLine(pointsA[0]); // prints X: 3, Y: 4
        Console.WriteLine(pointsB[0]); // prints X: 3, Y: 4
        pointsA[0].X = 4; 
        pointsA[0].Y = 3;
        // After the modification
        Console.WriteLine(pointsA[0]); // prints X: 4, Y: 3
        Console.WriteLine(pointsB[0]); // prints X: 4, Y: 3

        Console.ReadKey();
    }
}
Christos
  • 53,228
  • 8
  • 76
  • 108