假設我有下面的代碼,以深克隆a到b我應該使用ToList()深層克隆IList嗎?
IList<int> a = new List<int>();
a.Add(5);
IList<int> b = a.ToList();
好還是壞? ToList似乎工作返回一個新的列表。但是當我谷歌它,其他人總是使用像
listToClone.Select(item => (T)item.Clone()).ToList();
什麼差異?
假設我有下面的代碼,以深克隆a到b我應該使用ToList()深層克隆IList嗎?
IList<int> a = new List<int>();
a.Add(5);
IList<int> b = a.ToList();
好還是壞? ToList似乎工作返回一個新的列表。但是當我谷歌它,其他人總是使用像
listToClone.Select(item => (T)item.Clone()).ToList();
什麼差異?
這取決於。如果你有一個value types的集合,它會複製它們。如果你有一個引用類型列表,那麼它只會複製對真實對象的引用,而不是真實值。這裏是一個小例子
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; }
}
在
一個前面的代碼的結果 - 1,2
b - 2,2
Ç - 年齡= 10
d - 年齡= 10
正如您所看到的,新集合中的第一個數字發生了變化,並未影響另一個數字。但是我創造的人的年齡並非如此。
如果您瞭解數據如何存儲,可以解釋它。有兩種存儲類型的數據,值類型和引用類型。這裏下面是一個基本類型的聲明和一個目的
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
v含alues。當我們想用克隆列表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
接口和this example來實現它。
我希望現在清楚了。
感謝您的解釋!很清楚。 – jojo
+1這樣的細節! –
如果內容的IList<T>
要麼包囊值直接確定不變的對象,用於在其中封裝的值的目的,或包封身份共享可變對象的狀態,則調用ToList
將創建一個新的列表,從原始分離的,它封裝了相同的數據。
但是,如果IList<T>
的內容封裝可變對象中的值或狀態,則此方法無效。對可變對象的引用只能是值,或者如果有問題的對象是非共享的。如果共享對可變對象的引用,則所有這些引用的下落將成爲封裝的狀態的一部分。爲了避免這種情況,複製這些對象的列表需要生成一個新的列表,其中包含對其他(可能是新創建的)對象的引用,這些對象封裝了與原始對象相同的一個或多個值。如果所討論的對象包含一個可用的Clone
方法,但可能適用於此目的,但如果所討論的對象本身是集合,則其方法的正確和有效行爲將依賴於它們知道它們是否包含必須的對象不會暴露給複製列表的接收者 - .NET Framework無法告訴他們的東西。
這取決於。如果你有一個值類型的列表,它會複製它們。如果你有一個引用類型列表,那麼它只會複製引用而不是對象。 –
第一種方法只是淺層克隆。第二種方法可能是深度克隆 - 它取決於「克隆」的實現。 – Enigmativity