简体   繁体   English

我应该使用ToList()Deep Clone IList吗?

[英]Should I use ToList() Deep Clone IList?

Assume I have below code to deep clone a to b 假设我有以下代码深度克隆a到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. 它看起来像ToList返回一个新列表。 But when I google it, others always use things like 但是当我谷歌时,其他人总是使用像

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

what the diff? 什么差异?

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 然后是MyInt

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 下面是创建整数和MyInt对象列表的示例

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. 现在,您可以看到列表myInts再次包含引用,而ints包含值。 When we want to clone a list using ToList() , 当我们想要使用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. 然后0x12340是第一个MyInt对象的引用,保持变量0.这里显示的是简化的解释。

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. 但是当我们想要更改克隆列表的变量时,第一个变量将设置为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. 在第一个值0x652310得到了改变至7但在MyInt对象,参考地址没得到了改变。 However, the value will be assigned to the 0x12340 address. 但是,该值将分配给0x12340地址。

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. 正如你所看到的,原始的ints保持了它的值, 原始的myInts不同的值,它被改变了。 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. Clone()来自Object类,它将创建一个具有相同变量的新对象。

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. 您可以通过实现ICloneable接口以及实现它的这个示例来实现。

I hope that it's now clear for you. 我希望现在对你来说很清楚。

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 a - 1,2

b - 2, 2 b - 2,2

c - Age = 10 c - 年龄= 10岁

d - Age = 10 d - 年龄= 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. 但我所创造的人的年龄并非如此。

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. 如果IList<T>的内容直接封装值,识别不可变对象以封装其中的值,或者封装共享可变对象的标识 ,则调用ToList将创建一个与原始分离的新列表,其中封装了相同的数据。

If the contents of an IList<T> encapsulate values or states in mutable objects, however, such an approach would not work. 但是,如果IList<T>的内容封装了可变对象中的值或状态,则这种方法不起作用。 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. 如果有问题的对象包含可用的Clone方法,那么可能可用于此目的,但如果有问题的对象本身就是集合,那么Clone方法的正确和有效行为将依赖于它们知道它们是否包含必须包含的对象不要暴露给复制列表的收件人 - 这是.NET Framework无法告诉他们的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM