C# 修改基类List中某一元素的子类类型
描述:
基类: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; }
}