如果您了解数据是如何存储的,则可以对其进行解释。数据有两种存储类型,值类型和引用类型。下面是一个原始类型和对象的声明示例
int i = 0;
MyInt myInt = new MyInt(0);
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;
}
}
这将如何存储在内存中?下面是一个例子。 请注意,以下所有内存示例均已简化!
_______ __________
| i | 0 |
| | |
| myInt | 0xadfdf0 |
|_______|__________|
对于您在代码中创建的每个对象,您将创建对所述对象的引用。该对象将被分组到一个堆中。栈内存和堆内存的区别请参考this explanation。
现在,回到您的问题,克隆一个列表。下面是创建整数列表和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));
}
然后我们看看内存,
_______ __________
| ints | 0x856980 |
| | |
| myInts| 0xa02490 |
|_______|__________|
由于列表来自一个集合,所以每个字段都包含一个引用地址,从而导致下一个
___________ _________
| 0x856980 | 1 |
| 0x856981 | 2 |
| 0x856982 | 3 |
| 0x856983 | 4 |
| 0x856984 | 5 |
| | |
| | |
| | |
| 0xa02490 | 0x12340 |
| 0xa02491 | 0x15631 |
| 0xa02492 | 0x59531 |
| 0xa02493 | 0x59421 |
| 0xa02494 | 0x59921 |
|___________|_________|
现在您可以看到列表myInts 再次包含引用,而ints 包含值。当我们想用ToList()克隆一个列表时,
List<int> cloned_ints = ints.ToList();
List<MyInt> cloned_myInts = myInts.ToList();
我们得到如下结果。
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 | | | |
|___________|_________| |___________|_________|
0x12340 是第一个MyInt 对象的引用,包含变量 0。这里简化显示以很好地解释它。
您可以看到列表显示为克隆。但是当我们要更改克隆列表的一个变量时,第一个变量会被设置为 7。
cloned_ints[0] = 7;
cloned_myInts[0].SetMyInt(7);
然后我们得到下一个结果
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 | | | |
|___________|_________| |___________|_________|
你看到变化了吗? 0x652310 中的第一个值已更改为 7。但在 MyInt 对象处,引用地址没有更改。但是,该值将分配给0x12340 地址。
当我们要显示结果时,我们有下一个
ints[0] -------------------> 1
cloned_ints[0] -------------> 7
myInts[0].GetMyInt() -------> 7
cloned_myInts[0].GetMyInt() -> 7
如您所见,原始ints 保持其值而原始myInts 具有不同 值,它已更改。这是因为两个指针都指向 same 对象。如果你编辑那个对象,两个指针都会调用那个对象。
这就是为什么有两种类型的克隆,一种是深的,一种是浅的。这里下面的例子是一个深度克隆
listToClone.Select(item => (T)item.Clone()).ToList();
这会选择原始列表中的每个项目,并克隆列表中找到的每个对象。 Clone() 来自 Object 类,它将创建一个具有相同变量的新对象。
但是,请注意,如果您的类中有对象或任何引用类型是不安全的,您必须自己实现克隆机制。或者您将面临与上述相同的问题,即原始对象和克隆对象只是持有一个引用。您可以通过实现ICloneable 接口和实现它的this example 来做到这一点。
我希望你现在清楚了。