Domanda

Assume I have below code to deep clone a to b

IList<int> a = new List<int>();
a.Add(5);
IList<int> b = a.ToList();

bad or good? It's seems work as ToList return a new List. But when I google it, others always use things like

listToClone.Select(item => (T)item.Clone()).ToList();

what the diff?

È stato utile?

Soluzione 2

It depends. If you have a collection of value types it will copy them. If you have a list of reference types then it will only copy references to real objects and not the real values. Here's a small example

void Main()
{
    var a = new List<int> { 1, 2 };
    var b = a.ToList();
    b[0] = 2;
    a.Dump();
    b.Dump();
    
    var c = new List<Person> { new Person { Age = 5 } };
    var d = c.ToList();
    d[0].Age = 10;
    c.Dump();
    d.Dump();
}

class Person
{
    public int Age { get; set; }
}

The previous code results in

a - 1, 2

b - 2, 2

c - Age = 10

d - Age = 10

As you can see the first number in the new collection changed and did not affect the other one. But that was not the case with the age of the person I created.

Altri suggerimenti

It can be explained if you understand how data got stored. There are two storage types of data, value type and reference type. Here below is an example of a declaration of a primitive type and an object

int i = 0;
MyInt myInt = new MyInt(0);

The MyInt class is then

public class MyInt {
    private int myint;

    public MyInt(int i) {
        myint = int;
    }

    public void SetMyInt(int i) {
        myint = i;
    }
    public int GetMyInt() {
        return myint;
    }
}

How would that be stored in memory ? Here below is an example. Please note that all memory examples here below are simplified !

 _______ __________
|   i   |     0    |
|       |          |
| myInt | 0xadfdf0 |
|_______|__________|

For each object that you are creating in your code, you will create a reference to the said object. The object will be grouped in a heap. For a difference between stack and heap memory, please refer to this explanation.

Now, back to your question, cloning a list. Here below is an example of creating lists of integers and MyInt objects

List<int> ints = new List<int>();
List<MyInt> myInts = new List<MyInt>();
// assign 1 to 5 in both collections
for(int i = 1; i <= 5; i++) {
    ints.Add(i);
    myInts.Add(new MyInt(i));
}

Then we look to the memory,

 _______ __________
| ints  | 0x856980 |
|       |          |
| myInts| 0xa02490 |
|_______|__________|

Since lists comes from a collection, each field contains a reference address, which leads to the next

 ___________ _________
| 0x856980  |   1     |
| 0x856981  |   2     |
| 0x856982  |   3     |
| 0x856983  |   4     |
| 0x856984  |   5     |
|           |         |
|           |         |
|           |         |
| 0xa02490  | 0x12340 |
| 0xa02491  | 0x15631 |
| 0xa02492  | 0x59531 |
| 0xa02493  | 0x59421 |
| 0xa02494  | 0x59921 |
|___________|_________|

Now you can see that the list myInts contains again references while ints contains values. When we want to clone a list using ToList(),

List<int> cloned_ints = ints.ToList();
List<MyInt> cloned_myInts = myInts.ToList();

we get a result like here below.

       original                    cloned
 ___________ _________      ___________ _________
| 0x856980  |   1     |    | 0x652310  |   1     |
| 0x856981  |   2     |    | 0x652311  |   2     |
| 0x856982  |   3     |    | 0x652312  |   3     |
| 0x856983  |   4     |    | 0x652313  |   4     |
| 0x856984  |   5     |    | 0x652314  |   5     |
|           |         |    |           |         |
|           |         |    |           |         |
|           |         |    |           |         |
| 0xa02490  | 0x12340 |    | 0xa48920  | 0x12340 |
| 0xa02491  | 0x15631 |    | 0xa48921  | 0x12340 |
| 0xa02492  | 0x59531 |    | 0xa48922  | 0x59531 |
| 0xa02493  | 0x59421 |    | 0xa48923  | 0x59421 |
| 0xa02494  | 0x59921 |    | 0xa48924  | 0x59921 |
|           |         |    |           |         |
|           |         |    |           |         |
| 0x12340   |   0     |    |           |         |
|___________|_________|    |___________|_________|

The 0x12340 is then the reference of the first MyInt object, holding variable 0. It's shown here simplified to explain it well.

You can see that the list appears as cloned. But when we want to change a variable of the cloned list, the first one will be set to 7.

cloned_ints[0] = 7;
cloned_myInts[0].SetMyInt(7);

Then we get the next result

       original                    cloned
 ___________ _________      ___________ _________
| 0x856980  |   1     |    | 0x652310  |   7     |
| 0x856981  |   2     |    | 0x652311  |   2     |
| 0x856982  |   3     |    | 0x652312  |   3     |
| 0x856983  |   4     |    | 0x652313  |   4     |
| 0x856984  |   5     |    | 0x652314  |   5     |
|           |         |    |           |         |
|           |         |    |           |         |
|           |         |    |           |         |
| 0xa02490  | 0x12340 |    | 0xa48920  | 0x12340 |
| 0xa02491  | 0x15631 |    | 0xa48921  | 0x12340 |
| 0xa02492  | 0x59531 |    | 0xa48922  | 0x59531 |
| 0xa02493  | 0x59421 |    | 0xa48923  | 0x59421 |
| 0xa02494  | 0x59921 |    | 0xa48924  | 0x59921 |
|           |         |    |           |         |
|           |         |    |           |         |
| 0x12340   |   7     |    |           |         |
|___________|_________|    |___________|_________|

Did you see the changes ? The first value in 0x652310 got changed to 7. But at the MyInt object, the reference address didn't got changed. However, the value will be assigned to the 0x12340 address.

When we want to display the result, then we have the next

ints[0]   -------------------> 1
cloned_ints[0]  -------------> 7

myInts[0].GetMyInt()  -------> 7
cloned_myInts[0].GetMyInt() -> 7

As you can see, the original ints has kept its values while the original myInts has a different value, it got changed. That's because both pointers points to the same object. If you edit that object, both pointers will call that object.

That's why there are two types of clonings, deep and shallowed one. The example below here is a deep clone

listToClone.Select(item => (T)item.Clone()).ToList();

This selects each item in the original list, and clones each found object in the list. The Clone() comes from the Object class, which will create a new object with same variables.

However, please notice that it's not secure if you have an object or any reference types in your class, you have to implement the cloning mechanism yourself. Or you will face same issues as described here above, that the original and cloned object is just holding a reference. You can do that by implmenting the ICloneable interface, and this example of implementing it.

I hope that it's now clear for you.

If the contents an IList<T> either encapsulate values directly, identify immutable objects for the purpose of encapsulating the values therein, or encapsulate the identities of shared mutable objects, then calling ToList will create a new list, detached from the original, which encapsulates the same data.

If the contents of an IList<T> encapsulate values or states in mutable objects, however, such an approach would not work. References to mutable objects can only values or if the objects in question are unshared. If references to mutable objects are shared, the whereabouts of all such references will become an part of the state encapsulated thereby. To avoid this, copying a list of such objects requires producing a new list containing references to other (probably newly-created) objects that encapsulate the same value or states as those in the original. If the objects in question include a usable Clone method, that might be usable for the purpose, but if the objects in question are collections themselves, the correct and efficient behavior of their Clone method would rely upon them knowing the whether they contain objects which must not be exposed to the recipient of the copied list--something which the .NET Framework has no way of telling them.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top