List未实例化就调用Add会抛NullReferenceException;正确写法是new List()或C#9+的[];类字段需在构造函数或声明时初始化。

为什么 List 初始化后 Add 会报“未将对象引用设置到对象的实例”
这是最常踩的坑:声明了 List 变量但没用 new 实例化。C# 中所有引用类型变量默认值是 null,调用 Add 就会直接抛 NullReferenceException。
- 错误写法:
List→ 后续names; names.Add("a")必崩 - 正确写法:
List或 C# 9+ 的names = new List (); Listnames = []; - 如果在类字段中声明,记得在构造函数里初始化,或用
= new List直接赋默认值()
List 和 Array 在扩容和性能上到底差在哪
List 底层用数组实现,但自动管理容量;Array 长度固定。关键差异不在“能不能变长”,而在“怎么变长”和“何时变长”。
- 首次创建空
List,内部数组长度为 0;第一次() Add后自动分配长度为 4 的数组 - 当容量不足时,
List默认按 2 倍扩容(如从 4→8→16),每次扩容都要Array.Copy,有性能开销 - 如果已知最终大小(比如要存 1000 条日志),直接用
new List预分配容量,避免多次复制(1000) -
Array没有扩容机制,想“变长”只能Array.Resize(本质也是新建+复制),不如List封装得干净
用 foreach 遍历 List 时修改集合会怎样
运行时直接抛 InvalidOperationException:“集合已修改;可能无法执行枚举操作”。这不是语法限制,而是 List 内部维护了一个版本号(_version),只要调用 Add、Remove、Clear 等任何改变结构的方法,版本号就变,枚举器检测到不匹配就炸。
- 安全做法:需要边遍历边删,改用
for (int i = list.Count - 1; i >= 0; i--)倒序删 - 或者先收集待删项:
var toRemove = list.Where(x => x.IsExpired).ToList();,再list.RemoveAll(x => x.IsExpired) - 注意:
list[i] = newValue(仅改值)不会触发版本号变更,允许在foreach中做
List 转成数组或只读集合的几种方式及适用场景
不是所有“转”都等价——有的只是视图包装,有的是深拷贝,有的还带线程安全语义。
-
list.ToArray():返回新数组,内容拷贝,后续修改互不影响;适合传给只接受T[]的老 API -
list.AsReadOnly():返回ReadOnlyCollection,底层仍指向原List,只是封掉了修改方法;适合暴露给外部但不想让对方改内部状态 -
list.ToList():看似多余,实则有用——比如你有一个IEnumerable,调用.ToList()强制立即执行并缓存结果,避免多次枚举副作用 - 别用
(IList强转来“假装只读”,这毫无意义,接口转换不改变可变性)list
实际项目里最容易被忽略的是预分配容量和枚举时修改集合这两条。前者影响性能,后者直接 crash,而且错误堆栈不直观——它不报在 Add 那行,而报在 foreach 的 MoveNext 里。










