最近碼牆時發現了一個很有意思的問題,定義一個引用對象,如果在循環外面定義對象,在循環裡list.add(對象),最後的結果卻是所有的對象值都是一樣的,即每add一次,都會把之前所有的數據覆蓋掉,蠻有趣的,在網上輕松的搜到了答案,把對象在循環裡new就行了,問題雖然解決了,但感覺這裡面包含了一些.net底層存儲的知識,有關於引用類型和值類型的存儲方式問題,寫了個demo總結了一下,水平,有大牛發現demo中有不足之處還請指正。
如下面兩圖:
圖1:在外面定義對象a,調試界面中可以看到,i=4時,之前list中所有對象都被覆蓋了
圖2:在循環裡定義對象a,不會被覆蓋
又用值類型(int,short之類的)試了一下,在循環外面不會被覆蓋,結果就不截圖了,實驗了以後,用一個大牛總結的一句話來說就是:對於List<T>來說,如果T是引用類型,那保存的是引用,如果是值類型,保存的是值本身!
但是上面的總結中有個特列:string類型。string在.net中很特殊,.net官方把它歸到了引用類型中,但它卻和值類型特別相似,具體講解參考園友停留的風的這篇文章:http://www.cnblogs.com/yank/archive/2011/10/24/2204145.html。
用string實驗結果如下:
顯而易見,string不會覆蓋之前的數據,是引用類型中的特列。
深入的思考了一下覆蓋的原因(水平有限,歡迎大家補充),應當如下:
1.對於引用類型,在循環外new了 a 對象後,這個對象的引用地址就確定了,執行到第二次list.add()時,list[0]中保存的a對象和新加的list[1] a對象是同一個對象,使用的是同一個地址,也就是說在添加list[1]是,list[0]也被修改了,因為它倆指針指向同一個地址,同樣,後面添加的都會修改前面所有對象,結果就是list最後所有數據都是最後的list[end_num]。
2.string也是引用對象,有唯一的引用地址(假設分配的是address1),但當add list[1]的時候(str值改變時),.net會檢查內存,發現不等於之前的字符串(list[1]不等於list[0]),.net會給原來的str重新分配內存空間address2,存儲list[0],而list[1]使用原來的空間address1。這樣使得每次list.add時都不會覆蓋之前的元素,因為它們使用的是不同的地址空間。
這也解釋了我之前socket項目中接收數據是用StringBuilder,而不是string,每次改變string時,會消耗一份內存,socket接收中頻繁的處理string對象,會消耗大量的內存。
感覺這次小實驗挖出了很多東西,要深入研究一下。