一、C# 集合体系概览

C# 的集合类型主要分布在两个核心命名空间:

  • System.Collections:非泛型集合,适用于.NET 早期版本,代表类型有ArrayListHashtableQueue等;
  • System.Collections.Generic:泛型集合(.NET 2.0+),类型安全且性能更优,代表类型有List<T>Dictionary<TKey, TValue>HashSet<T>等。

其中,ArrayListList<T>作为动态数组的典型实现,是日常开发中最常用的集合类型,也是理解 C# 集合设计思想的关键。

二、ArrayList:非泛型动态数组

2.1 ArrayList 核心特性

ArrayList是 C# 早期的动态数组实现,本质是封装了object[]数组的类,核心特性如下:

  • 动态扩容:底层基于数组实现,初始容量为 0,添加第一个元素时扩容至 4,后续自动按需扩容;
  • 非类型安全:所有元素均以object类型存储,值类型会发生装箱 / 拆箱操作;
  • 灵活存储:允许存储null值、重复元素,支持任意类型的对象;
  • 索引访问:支持数组式的索引器访问,时间复杂度 O (1)。

2.2 ArrayList 常用操作

1. 初始化

ArrayList提供三种初始化方式,适配不同场景:

// 空列表(初始容量0)
ArrayList list1 = new ArrayList();
// 指定初始容量(减少扩容次数,提升性能)
ArrayList list2 = new ArrayList(100);
// 从其他集合初始化
ArrayList list3 = new ArrayList(new int[] { 1, 2, 3 });
2. 元素增删

ArrayList提供丰富的元素操作方法,覆盖单个 / 批量、尾部 / 指定位置的增删需求:

ArrayList list = new ArrayList();

// 1. 添加元素
list.Add("C#"); // 尾部添加单个元素
list.Add(123);  // 值类型自动装箱(int → object)
list.AddRange(new object[] { 4.5, DateTime.Now }); // 批量添加
list.Insert(1, "插入到索引1的位置"); // 指定索引插入
list.InsertRange(2, new ArrayList { "a", "b" }); // 批量插入

// 2. 删除元素
list.Remove("C#"); // 按值删除第一个匹配项
list.RemoveAt(0);  // 按索引删除
list.RemoveRange(1, 2); // 从索引1开始删除2个元素
list.Clear(); // 清空所有元素
3. 元素查找与访问
ArrayList list = new ArrayList { "A", "B", "C", "B" };

// 检查元素是否存在
bool hasB = list.Contains("B"); // true
// 查找元素索引
int firstBIndex = list.IndexOf("B"); // 1
int lastBIndex = list.LastIndexOf("B"); // 3
// 索引器访问(需显式类型转换)
string item = (string)list[0]; // "A"
// 排序后二分查找(需先Sort)
list.Sort();
int cIndex = list.BinarySearch("C"); // 2

2.3 ArrayList 的局限性

尽管ArrayList灵活,但存在明显短板:

  • 性能损耗:值类型的装箱 / 拆箱操作增加内存开销和性能消耗;
  • 类型不安全:编译时无法校验元素类型,运行时可能抛出InvalidCastException
  • 现代开发不推荐:仅适用于遗留代码或.NET 1.x 环境,新代码优先使用泛型集合。

三、List<T>:类型安全的泛型动态数组

List<T>ArrayList的泛型替代版本,解决了非泛型集合的类型安全和性能问题,是当前 C# 开发的首选动态数组。

3.1 List<T>核心优势

ArrayList相比,List<T>的核心改进在于:

特性 ArrayList List<T>
类型安全 非泛型,object 存储 泛型,编译时类型校验
性能 装箱 / 拆箱损耗 无装箱操作,性能更优
内存占用 较高(object 引用) 更低(直接存储值类型)
错误排查 运行时类型转换异常 编译时类型错误提示

3.2 List<T>常用操作

1. 初始化与基础操作

// 空列表
List<int> numbers = new List<int>();
// 初始化赋值
List<string> names = new List<string> { "Alice", "Bob" };
// 指定初始容量
List<double> values = new List<double>(10);

// 添加元素
numbers.Add(1);
numbers.AddRange(new int[] { 2, 3, 4 });
// 访问与修改
int first = numbers[0]; // 无需拆箱
numbers[1] = 10;
// 基础查询
int count = numbers.Count;
bool has3 = numbers.Contains(3);

2. 高级操作:排序

List<T>Sort方法支持多种排序方式,满足复杂场景需求:

方式 1:默认排序(实现 IComparable<T>)

自定义类型需实现IComparable<T>接口,重写CompareTo方法:

public class Student : IComparable<Student>
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }

    // 按Id升序排序
    public int CompareTo(Student other)
    {
        if (this.Id > other.Id) return 1;
        if (this.Id < other.Id) return -1;
        return 0;
    }
}

// 排序使用
List<Student> students = new List<Student>
{
    new Student { Id = 1, Name = "张三" },
    new Student { Id = 3, Name = "李四" },
    new Student { Id = 2, Name = "王五" }
};
students.Sort(); // 按Id升序排列
方式 2:自定义比较器(IComparer<T>)

适用于多种排序规则的场景,无需修改实体类:

// 按年龄降序的比较器
public class StudentAgeComparer : IComparer<Student>
{
    public int Compare(Student x, Student y)
    {
        return y.Age.CompareTo(x.Age);
    }
}

// 使用比较器排序
students.Sort(new StudentAgeComparer());
方式 3:Lambda 表达式(Comparison<T>)

适用于临时排序需求,简化代码:

// 按年龄降序排序
students.Sort((x, y) => y.Age.CompareTo(x.Age));

3. 高级操作:查找与转换

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

// 查找第一个偶数
int firstEven = numbers.Find(n => n % 2 == 0);
// 查找所有偶数
List<int> allEvens = numbers.FindAll(n => n % 2 == 0);
// 转换为字符串列表
List<string> strNumbers = numbers.ConvertAll(n => n.ToString());
// 转换为数组
int[] numArray = numbers.ToArray();

四、ArrayList 与 List<T>核心对比

表格

维度 ArrayList List<T>
类型系统 非泛型(object) 泛型(强类型)
性能 装箱 / 拆箱损耗 无装箱,性能更高
类型安全 运行时可能出错 编译时类型校验
内存占用 较高 更低
适用场景 遗留代码、混合类型存储 现代开发、类型明确的集合
引入版本 .NET 1.0 .NET 2.0+

五、最佳实践

  1. 优先使用 List<T>:新开发项目中,无论值类型还是引用类型,均推荐使用List<T>,兼顾性能与类型安全;
  2. 避免 ArrayList:仅在维护.NET 1.x 遗留代码或必须存储混合类型时使用ArrayList
  3. 指定初始容量:当已知集合大致大小,初始化时指定容量(如new List<int>(100)),减少自动扩容的性能损耗;
  4. 排序优化:自定义类型排序优先使用IComparer<T>Comparison<T>,避免修改实体类的IComparable<T>实现;
  5. 减少装箱操作:若需存储混合类型,可使用List<object>替代ArrayList,保持泛型特性。

总结

ArrayList作为 C# 早期的动态数组实现,见证了.NET 集合体系的发展;而List<T>作为泛型时代的产物,凭借类型安全、高性能的优势成为现代 C# 开发的首选。理解两者的设计思想与使用差异,不仅能帮助开发者写出更高效的代码,也能深入掌握 C# 类型系统的核心逻辑。在实际开发中,结合场景选择合适的集合类型,才能充分发挥 C# 的语言特性与性能优势。

更多推荐