描述:
基類:BaseClass
子類1:A
子類2:B
然后我有一個List<BaseClass>類型的鏈表:list,我先往list中添加了兩個元素:
第一個元素為A類型,第二個元素為B類型,
然后我想改變第一個元素類型由原來的A到B;
直接使用list.ElementAt(0)取出第一個元素a,然后new 了一個新的B對象,因為我以為a的引用和鏈表list的第一個位置list[0]的引用相同,實際上并不是這樣的。
如果不new新對象,改變a里面的屬性,會直接反饋到list[0]里面,但是new 了新對象的話,a和list[0]就不是同一個東西了;
內存部分分析:
1. 初始狀態(執行前)
var a = new A(); // 創建A實例 var b = new B(); // 創建B實例 var list = new List<BaseClass>(); // 創建列表實例
堆棧 (Stack) 內存:
地址 變量名 值 (堆地址) 0x1000 a → 0x2000 0x1004 b → 0x3000 0x1008 list → 0x4000
堆 (Heap) 內存:
地址 對象類型 數據 0x2000 A實例 {StrBase=null, StrA=null} 0x3000 B實例 {StrBase=null, StrB=null} 0x4000 List實例 [容量=4, 元素數組地址=0x5000] 0x5000 數組 [0x2000, null, null, null] ← 第一個元素指向A實例
2. 添加元素后
list.Add(a); list.Add(b);
堆棧 (Stack) 內存:(不變)
地址 變量名 值 (堆地址) 0x1000 a → 0x2000 0x1004 b → 0x3000 0x1008 list → 0x4000
堆 (Heap) 內存:
地址 對象類型 數據 0x2000 A實例 {StrBase=null, StrA=null} 0x3000 B實例 {StrBase=null, StrB=null} 0x4000 List實例 [容量=4, 元素數組地址=0x5000, 計數=2] 0x5000 數組 [0x2000, 0x3000, null, null] ← 第一個指向A,第二個指向B
3. 獲取元素引用
var aa = list.ElementAt(0);
堆棧 (Stack) 內存:
地址 變量名 值 (堆地址) 0x1000 a → 0x2000 0x1004 b → 0x3000 0x1008 list → 0x4000 0x100C aa → 0x2000 ← 新的變量aa,指向A實例
堆 (Heap) 內存:(不變)
地址 對象類型 數據 0x2000 A實例 {StrBase=null, StrA=null} 0x3000 B實例 {StrBase=null, StrB=null} 0x4000 List實例 [容量=4, 元素數組地址=0x5000, 計數=2] 0x5000 數組 [0x2000, 0x3000, null, null]
4. 關鍵操作:重新賦值
aa = new B(); // 創建新的B實例
堆棧 (Stack) 內存:
地址 變量名 值 (堆地址) 0x1000 a → 0x2000 0x1004 b → 0x3000 0x1008 list → 0x4000 0x100C aa → 0x6000 ← aa現在指向新的B實例!
堆 (Heap) 內存:
地址 對象類型 數據 0x2000 A實例 {StrBase=null, StrA=null} 0x3000 B實例 {StrBase=null, StrB=null} 0x4000 List實例 [容量=4, 元素數組地址=0x5000, 計數=2] 0x5000 數組 [0x2000, 0x3000, null, null] ← 第一個元素仍然指向A實例! 0x6000 B實例 {StrBase=null, StrB=null} ← 新創建的B實例
關鍵點說明
1. 引用類型的本質
變量存儲在堆棧:
a
,?b
,?list
,?aa
?這些變量都存儲在堆棧中對象存儲在堆中:實際的A、B實例和List實例存儲在堆中
變量存儲的是引用:堆棧中的變量存儲的是堆中對象的地址
2. 賦值操作的含義
aa = new B();
這條語句的實際作用是:
在堆中創建新的B實例(地址0x6000)
將堆棧中變量
aa
的值從0x2000
改為0x6000
不影響列表內部數組中存儲的引用值
3. 正確的修改方式
如果要修改列表中的元素,應該:
// 方式1:直接修改列表元素 list[0] = new B(); // 這會修改數組[0]位置的引用// 方式2:通過引用修改 ref var elementRef = ref list[0]; elementRef = new B();
4. 內存變化對比
錯誤的方式(你的代碼):
堆棧: aa → 0x6000 (新B實例) 堆: 數組[0] → 0x2000 (原來的A實例) ← 未改變!
正確的方式:
堆棧: aa → 0x6000 (新B實例) 堆: 數組[0] → 0x6000 (新B實例) ← 已改變!
總結
從堆棧內存的角度看:
aa = new B()
?只改變了堆棧中變量aa
存儲的地址值列表的內部數組存儲在堆中,其元素引用保持不變
要修改列表內容,必須直接操作列表本身,而不是操作從列表獲取的臨時變量
測試代碼:
static void Main(string[] args)
{Console.WriteLine("Hello, World!");var a = new A();a.StrBase = "A";var b = new B();//var list = new List<BaseClass>();var list = new BaseClass[2];list[0]=a;list[1]=b;//list.Add(b);//var aa = list.ElementAt(0);var aa = list.FirstOrDefault(s => s.StrBase.Equals("A"));aa = new B();//aa.StrBase =" new B()";int c = 0;
}
public class A : BaseClass
{
public string StrA { get; set; }
}
public class B : BaseClass
{
public string StrB { get; set; }
}
public class BaseClass
{public string StrBase { get; set; }
}