目录
-
- 一、List<T>是什么?先看一个生活类比
- 二、List<T>基础用法:从创建到常用操作
-
- [1. 最简单的例子:创建一个 "整数盒子"](#1. 最简单的例子:创建一个 "整数盒子")
- [2. 进阶操作:增删改查全掌握](#2. 进阶操作:增删改查全掌握)
- [三、新手必踩的 5 个坑,附解决方案](#三、新手必踩的 5 个坑,附解决方案)
-
- [坑 1:未初始化就用,直接报空引用](#坑 1:未初始化就用,直接报空引用)
- [坑 2:往 List 里塞 null,取用时翻车](#坑 2:往 List 里塞 null,取用时翻车)
- [坑 3:索引越界,以为 "Count 等于最大索引"](#坑 3:索引越界,以为 "Count 等于最大索引")
- [坑 4:遍历同时修改集合,直接崩溃](#坑 4:遍历同时修改集合,直接崩溃)
- [坑 5:用 Contains 判断自定义对象,永远返回 false](#坑 5:用 Contains 判断自定义对象,永远返回 false)
- [四、为什么 MVC 里到处都是 List<T>?](#四、为什么 MVC 里到处都是 List<T>?)
- 互动时间
大家好,我是William_cl。今天咱们聊一个 C# 里几乎天天用,但新手容易踩坑的东西 ------ 泛型集合List。如果你写代码时还在纠结 "数组长度不够用"、"存数据总担心类型错",那这篇文章一定要看完。
一、List是什么?先看一个生活类比
你家里肯定有这样的收纳盒:
- 专门放袜子的抽屉(只装袜子,不乱)
- 专门放充电器的盒子(线再多,不会和袜子混一起)
List就像这种 "带标签的收纳盒",就是标签,比如List是 "只装整数的盒子",List是 "只装字符串的盒子"。
对比一下 "旧时代的盒子": - 普通数组int[]:容量固定,装满了想多放一个都不行(比如定义int[3],最多放 3 个元素)
- 非泛型集合ArrayList:啥都能装(int、string、对象混着来),取出来时经常认错类型
而List的优势就俩字:灵活(容量自动扩)+安全(只装指定类型)。
二、List基础用法:从创建到常用操作
1. 最简单的例子:创建一个 "整数盒子"
csharp
using System;
using System.Collections.Generic; // 注意:必须引用这个命名空间
class Program
{
static void Main(string[] args)
{
// 创建一个只能装int的List(标签是int)
List<int> numbers = new List<int>();
// 往里面加元素(Add方法)
numbers.Add(10);
numbers.Add(20);
numbers.Add(30);
// 直接打印数量(Count属性)
Console.WriteLine($"当前有{numbers.Count}个元素"); // 输出:3
// 访问单个元素(和数组一样用索引)
Console.WriteLine("第二个元素是:" + numbers[1]); // 输出:20
// 遍历所有元素(foreach最方便)
Console.WriteLine("所有元素:");
foreach (int num in numbers)
{
Console.WriteLine(num);
}
}
}
2. 进阶操作:增删改查全掌握
csharp
// 1. 初始化时直接填数据
List<string> fruits = new List<string> { "苹果", "香蕉", "橙子" };
// 2. 插入元素(在指定位置加)
fruits.Insert(1, "草莓"); // 在索引1的位置插入,结果:苹果、草莓、香蕉、橙子
// 3. 删除元素(两种方式)
fruits.Remove("香蕉"); // 直接删值:苹果、草莓、橙子
fruits.RemoveAt(0); // 删索引0的元素:草莓、橙子
// 4. 查找元素
bool hasOrange = fruits.Contains("橙子"); // 有没有橙子?true
int index = fruits.IndexOf("草莓"); // 草莓在哪个位置?0
// 5. 清空所有元素
fruits.Clear();
Console.WriteLine(fruits.Count); // 输出:0
三、新手必踩的 5 个坑,附解决方案
坑 1:未初始化就用,直接报空引用
csharp
List<int> nums;
nums.Add(1); // 报错:未将对象引用设置到对象的实例
原因:只声明了变量,没new List<int>()创建实例,就像买了个空盒子但没拆开用。解决:声明时直接初始化:List<int> nums = new List<int>();
坑 2:往 List 里塞 null,取用时翻车
csharp
List<string> names = new List<string>();
names.Add(null); // 允许添加null
string first = names[0];
int length = first.Length; // 报错:未将对象引用设置到对象的实例
原因: List允许添加 null(除非 T 是值类型如 int),但后续操作 null 会报错。
解决: 添加前判断,或遍历前检查:
csharp
// 添加时过滤null
if (name != null) names.Add(name);
// 遍历时有备无患
foreach (var n in names)
{
if (n != null)
{
Console.WriteLine(n.Length);
}
}
坑 3:索引越界,以为 "Count 等于最大索引"
csharp
List<int> list = new List<int> { 1, 2, 3 };
Console.WriteLine(list[3]); // 报错:索引超出范围
原因: Count是元素总数,最大索引是Count-1(3 个元素,索引 0、1、2)。
解决: 访问前先判断:
csharp
if (index >= 0 && index < list.Count)
{
// 安全访问
}
坑 4:遍历同时修改集合,直接崩溃
csharp
List<int> numbers = new List<int> { 1, 2, 3 };
foreach (int num in numbers)
{
if (num == 2)
{
numbers.Remove(num); // 报错:集合已修改,可能无法执行枚举操作
}
}
原因: foreach 遍历是 "只读模式",中途增删元素会破坏遍历状态。
解决: 用 for 循环倒序遍历(避免索引偏移):
csharp
for (int i = numbers.Count - 1; i >= 0; i--)
{
if (numbers[i] == 2)
{
numbers.RemoveAt(i); // 安全删除
}
}
坑 5:用 Contains 判断自定义对象,永远返回 false
csharp
public class Person
{
public string Name { get; set; }
public Person(string name) { Name = name; }
}
// 测试代码
List<Person> people = new List<Person>();
people.Add(new Person("张三"));
// 想判断是否有"张三"
bool hasZhang = people.Contains(new Person("张三")); // 结果:false
原因: Contains默认比较 "对象地址"(引用类型),两个new Person("张三")是不同对象,地址不同。
解决: 让 Person 类实现IEquatable接口,自定义比较规则:
csharp
public class Person : IEquatable<Person>
{
public string Name { get; set; }
public Person(string name) { Name = name; }
// 自定义比较逻辑:只要Name相同就认为相等
public bool Equals(Person other)
{
return other != null && Name == other.Name;
}
}
// 此时再调用Contains就会返回true了
四、为什么 MVC 里到处都是 List?
在 MVC 开发中,List简直是 "刚需":
- 控制器(Controller)从数据库查数据,用List存商品列表
- 视图(View)接收List,用 foreach 遍历显示用户表格
- 模型(Model)里的集合属性,几乎都是List(如public List Orders { get; set; })
它就像 MVC 各层之间的 "数据传送带",安全又高效。
互动时间
你在使用List时踩过哪些奇葩的坑?有没有什么提高效率的小技巧(比如Capacity预分配容量)?欢迎在评论区分享,点赞最高的评论送我整理的《C# 集合操作手册》一份~
下一期咱们聊 LINQ,看看怎么用一行代码搞定 List 的复杂查询,不见不散!