1

Here is the problem:

I have this class Person :

class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Position { get; set; }
    public int Age { get; set; }
}

I initialized this object named workersA as the source object:

List<Person> workersA = new List<Person>()
{
   new Person { Id = 1, Name = "Emp1", Age = 27  },
   new Person { Id = 2, Name = "Emp2", Age = 18  },
   new Person { Id = 3, Name = "Emp3", Age = 23  },
};

I declare the other two instances as this:

List<Person> workersB = new List<Person>();
List<Person> workersC = new List<Person>();

And fill it with different approach:

On workersB stored from source using .ToList() extension.

workersB = workersA.ToList();

And on workersC stored from source using foreach statement.

foreach (var w in workersA.ToList())
{
    workersC.Add(w);
}

Now when i try to modify the Age property of the source object.. here's the code:

foreach (var p in workersA.ToList())
{
    p.Age--;
}

And output all the result using this code:

Console.WriteLine("Wokers A:");
foreach (var p in workersA.ToList())
{
    Console.WriteLine("Id: {0} Name: {1}, Age: {2}", p.Id, p.Name, p.Age);
}

Console.WriteLine();
Console.WriteLine("Wokers B:");
foreach (var p in workersB.ToList())
{
    Console.WriteLine("Id: {0} Name: {1}, Age: {2}", p.Id, p.Name, p.Age);
}

Console.WriteLine();
Console.WriteLine("Wokers B:");
foreach (var p in workersB.ToList())
{
    Console.WriteLine("Id: {0} Name: {1}, Age: {2}", p.Id, p.Name, p.Age);
}

Here is what ive'd got:

Wokers A:
Id: 1 Name: Emp1, Age: 26
Id: 2 Name: Emp2, Age: 17
Id: 3 Name: Emp3, Age: 22

Wokers B:
Id: 1 Name: Emp1, Age: 26   // 27 - expectation
Id: 2 Name: Emp2, Age: 17   // 18 - expectation
Id: 3 Name: Emp3, Age: 22   // 22 - expectation

Wokers C:
Id: 1 Name: Emp1, Age: 26   //  27 - expectation
Id: 2 Name: Emp2, Age: 17   //  18 - expectation
Id: 3 Name: Emp3, Age: 22   //  22 - expectation

To address the question clearly

Why did i get the same output?

Shift 'n Tab
  • 8,808
  • 12
  • 73
  • 117

4 Answers4

2

Because, ultimately, every element in both the workersB and workersC will still point to individual Person instance you originally created. Thus they all refer to the same instances - thus you got identical results.

In short, you got three different lists having the three same Person instances

When you do this:

workersB = workersA.ToList();

You create a new instance of List, not the new instances of individual object in the List. Likewise, when you do this:

foreach (var w in workersA.ToList())
{
    workersC.Add(w);
}

You are passing the same Person reference in workersA to workersC. but they are all referring to the same Person instances.

And so when you finally do:

foreach (var p in workersA.ToList())
{
    p.Age--;
}

Every Person instance's Age is changed, though there are three different lists which contain them.


Solution:

If you really want to pass different Person to the three lists, one way is by implementing a Clone method for the Person (which you would find in IClonable interface):

public Person Clone(){
    return new Person() { Id = this.Id, Name = this.Name, Age = this.Age };
}

Then you input the other lists with the Clone rather than with the original Person:

foreach (var w in workersA.ToList())
{
    workersC.Add(w.Clone()); //use Clone here
}

Edit 2, OP's final solution according to the chat

class Person : ICloneable 
{     
    public int Id { get; set; } 
    public string Name { get; set; } 
    public string Position { get; set; } 
    public int Age { get; set; } 
    
    public Person Clone() 
    { 
        return (Person)this.MemberwiseClone(); 
    } 
     
    object ICloneable.Clone() 
    { 
        return this.Clone(); 
    } 

}
Community
  • 1
  • 1
Ian
  • 30,182
  • 19
  • 69
  • 107
  • @ShiftN'Tab ah yes, you should create a copy/clone of the three `Person`s rather than making three lists containing the same `Person`. One simple way is by making the `Person` having `Clone` method to create new `Person` everytime based on its own properties, and then you pass the cloned `Person` instead of the original `Person` – Ian Apr 20 '16 at 08:30
  • ill test this one, but `IClonable` interface could not be found – Shift 'n Tab Apr 20 '16 at 08:44
  • @ShiftN'Tab yep, you could make your `Person` inherit from `IClonable` to force the `Clonable` behavior if you wish. ;) – Ian Apr 20 '16 at 08:45
  • but `IClonable` interface could not be found, is there any assembly needed sir? – Shift 'n Tab Apr 20 '16 at 08:48
  • 1
    Sorry, I misspell it, it should be `ICloneable` (with `e`). It is in the `System` – Ian Apr 20 '16 at 08:49
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/109641/discussion-between-shift-n-tab-and-ian). – Shift 'n Tab Apr 20 '16 at 08:57
  • I will approved this one but prior to the correct implementation here it is based on your answer `class Person : ICloneable { public int Id { get; set; } public string Name { get; set; } public string Position { get; set; } public int Age { get; set; } public Person Clone() { return (Person)this.MemberwiseClone(); } } object ICloneable.Clone() { return this.Clone(); } }` – Shift 'n Tab Apr 20 '16 at 09:40
2

You get the same output because your Person class is a reference type. So, each of the three lists contain references to the same objects. Thus, if you change one, you change also the corresponding elements in the other two lists, because they point to the same element. If you want the workersB and workersC to contain independent elements, you have to copy them. For instance add a copy constructor

class Person {
   ....

   public Person(Person other) {
       this.Id = other.Id;
       this.Name = other.Name;
       ...
   }
}

and when adding the elements to workersB and workersC call that copy constructor.

 foreach (Person p in workersA) {
     workersB.Add(new Person(p));
     workersC.Add(new Person(p));
 }

or as an alternative

 workersB = workersA.Select(x=> new Person(x)).ToList();
 workersC = workersA.Select(x=> new Person(x)).ToList();

This way, all the elements are new objects and not related to each other.

derpirscher
  • 14,418
  • 3
  • 18
  • 35
  • does this one has a better performance than using ICloneable? – Shift 'n Tab Apr 20 '16 at 08:42
  • I did not look into performance. Just for the principle how to do it. But it may depend on how you implement `ICloneable.Clone()`. For best performance you may have a look at @Nikolai's anwer – derpirscher Apr 20 '16 at 09:03
2

In order to isolate the results you need to create separate instances. One of the ways to achieve is to clone your instances while creating new lists.

Take a look at this answer it covers the area quite comprehensively:

Fastest Way to do Shallow Copy in C#

Community
  • 1
  • 1
Nikolai
  • 198
  • 1
  • 14
1

as class Person is reference type (https://msdn.microsoft.com/en-us/library/t63sy5hs.aspx) your lists contain only the references to the instances of Person you created initially. When you change the instance in any list, the instance is changed in every list.

Sebastian Siemens
  • 2,302
  • 1
  • 17
  • 24