假设我有以下代码将 a 深度克隆到 b
IList<int> a = new List<int>();
a.Add(5);
IList<int> b = a.ToList();
好还是坏? 当 ToList 返回一个新列表时,这似乎是有效的。但当我用谷歌搜索时,其他人总是使用类似的东西
listToClone.Select(item => (T)item.Clone()).ToList();
有什么区别?
最佳答案
如果您了解数据的存储方式,就可以对其进行解释。数据的存储类型有两种,值类型和引用类型。下面是基本类型和对象声明的示例
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
具有不同值,已经改变了。这是因为两个指针都指向同一个对象。如果您编辑该对象,两个指针都会调用该对象。
这就是为什么有两种类型的克隆,深克隆和浅克隆。下面的示例是深度克隆
listToClone.Select(item => (T)item.Clone()).ToList();
这将选择原始列表中的每个项目,并克隆列表中每个找到的对象。 Clone()
来自 Object 类,它将创建一个具有相同变量的新对象。
但是,请注意,如果您的类中有对象或任何引用类型,那么它是不安全的,您必须自己实现克隆机制。或者您将面临与上面描述的相同的问题,即原始对象和克隆对象仅持有引用。您可以通过实现 ICloneable
来做到这一点接口(interface),以及this example实现它。
我希望您现在已经清楚了。
关于c# - 我应该使用 ToList() 深度克隆 IList 吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22729080/