C#中常见集合都有哪些?

文章目录

  • 为什么需要集合
  • 核心操作
  • **主要命名空间**
  • [**C# 集合的分类**](# 集合的分类**)
    • **按泛型支持分类**
      • [**非泛型集合 (Non-Generic Collections)**](#非泛型集合 (Non-Generic Collections))
      • [**泛型集合 (Generic Collections)**](#泛型集合 (Generic Collections))
    • **按可变性分类**
      • [**可变集合 (Mutable Collections)**](#可变集合 (Mutable Collections))
      • [**不可变集合 (Immutable Collections)**](#不可变集合 (Immutable Collections))
    • **按线程安全性分类**
      • [**非线程安全集合 (Non-Thread-Safe Collections)**](#非线程安全集合 (Non-Thread-Safe Collections))
      • [**线程安全集合 (Thread-Safe Collections)**](#线程安全集合 (Thread-Safe Collections))
    • **按访问方式和结构分类**
      • [**索引集合 (Indexed Collections)**](#索引集合 (Indexed Collections))
      • [**键控集合 (Keyed/Associative Collections)**](#键控集合 (Keyed/Associative Collections))
      • [**集合/集 (Set-Based Collections)**](#集合/集 (Set-Based Collections))
      • [**顺序访问集合 (Sequential Access - FIFO/LIFO)**](#顺序访问集合 (Sequential Access - FIFO/LIFO))
    • **按接口实现分类**
  • 常见集合
      • [**ArrayList (System.Collections)**](#ArrayList (System.Collections))
      • [**Hashtable (System.Collections)**](#Hashtable (System.Collections))
      • [**List<T> (System.Collections.Generic)**](#List<T> (System.Collections.Generic))
      • [**Dictionary<TKey, TValue> (System.Collections.Generic)**](#Dictionary<TKey, TValue> (System.Collections.Generic))
      • [**HashSet<T> (System.Collections.Generic)**](#HashSet<T> (System.Collections.Generic))

集合的概念

在 C#(以及许多其他编程语言)中,集合 是一个用来存储和管理一组其他对象(称为元素或项)的容器对象。
简单来说,当你需要处理多个相关的数据项时,将它们放入一个集合中通常比为每个项创建单独的变量更方便、更有效。想象一下你需要存储一个班级所有学生的名字,或者一个购物车里所有商品的列表,使用集合就非常合适。

为什么需要集合

  1. 数据分组: 将逻辑上相关的项组织在一起。
  2. 动态大小: 大多数集合类型的大小不是固定的(与数组 T[] 不同),它们可以根据需要动态地增加或减少容量来容纳更多或更少的元素。
  3. 高效操作: 集合提供了优化过的方法来执行常见操作,如添加 (Add)、移除 (Remove)、查找 (Find/Contains)、排序 (Sort) 等。其效率取决于集合的具体类型及其底层数据结构。
  4. 统一管理: 通过集合对象统一管理一组数据,简化代码逻辑。
  5. 迭代: 提供标准的机制(如 foreach 循环)来遍历集合中的所有元素。

核心操作

大多数集合都支持一些基本操作,例如:

  • 添加元素
  • 删除元素
  • 查找元素是否存在
  • 获取集合中元素的数量
  • 清空集合
  • 遍历(迭代)集合中的元素

底层数据结构

C# 中的集合类是常见数据结构(如动态数组、链表、哈希表、树等)的具体实现。选择哪种集合类型通常取决于你对性能(特定操作的速度)、内存使用、元素顺序、元素唯一性等方面的要求。

主要命名空间

C# 集合相关的类主要分布在以下几个命名空间:

  • System.Collections: 包含非泛型集合类(通常已不推荐在新代码中使用)。
  • System.Collections.Generic: 包含泛型集合类(推荐使用,提供类型安全和性能优势)。
  • System.Collections.ObjectModel: 包含一些用于创建自定义集合或提供特定功能(如 ObservableCollection<T>)的基类和集合。
  • System.Collections.Concurrent: 包含设计用于多线程环境的线程安全集合类。
  • System.Collections.Immutable: 包含不可变集合类,其内容在创建后无法更改。

C# 集合的分类

C# 中的集合可以从多个维度进行分类,理解这些分类有助于选择最适合特定场景的集合类型。

按泛型支持分类

这是最基本也是最重要的分类。

非泛型集合 (Non-Generic Collections)

命名空间: System.Collections

  • **特点:**可以存储任意类型的对象 (System.Object)。
  • 类型不安全:在添加元素时,所有类型都被视为 object;在检索元素时,通常需要进行显式类型转换(拆箱/Casting),如果在运行时类型不匹配,会抛出 InvalidCastException
  • 性能开销:当存储值类型(如 int, struct)时,会发生装箱(Boxing,将值类型转换为对象类型)和拆箱(Unboxing,将对象类型转换回值类型)操作,这会带来额外的内存分配和 CPU 开销。
  • 代表: ArrayList, Hashtable, Queue, Stack, SortedList.
  • 现状: 在现代 C# 开发中已不推荐使用,主要用于维护旧代码或与不支持泛型的旧 API 交互。

泛型集合 (Generic Collections)

命名空间: System.Collections.Generic (及其他如 Concurrent, Immutable 等)

  • **特点:**在创建集合时需要指定存储元素的具体类型(例如 List<string> 表示只能存储字符串的列表)。
  • 类型安全:编译器在编译时就会检查类型,防止将错误类型的对象添加到集合中,避免了运行时的类型转换错误。
  • 性能更好:对于值类型,避免了装箱和拆箱操作,性能通常优于对应的非泛型集合。
  • 代表: List<T>, Dictionary<TKey, TValue>, HashSet<T>, Queue<T>, Stack<T>, SortedList<TKey, TValue>, SortedDictionary<TKey, TValue>, LinkedList<T> 等。
  • 现状:强烈推荐在所有新代码中使用泛型集合。

按可变性分类

可变集合 (Mutable Collections)

  • 特点: 集合在创建后,其内容(元素)可以被修改,如添加、删除、更新。这是绝大多数标准集合的默认行为。
  • 例子: List<T>, Dictionary<TKey, TValue>, HashSet<T>, ArrayList, ObservableCollection<T> 等。

不可变集合 (Immutable Collections)

命名空间: System.Collections.Immutable

  • 特点: 一旦创建,集合实例的内容就不能更改。任何尝试"修改"的操作(如 Add, Remove)都会返回一个包含更改后的集合实例,原始实例保持不变。
  • 优点: 线程安全(多线程读取安全),易于推理(状态不会意外改变),适合函数式编程。
  • 例子: ImmutableList<T>, ImmutableDictionary<TKey, TValue>, ImmutableHashSet<T>.

按线程安全性分类

非线程安全集合 (Non-Thread-Safe Collections)

  • 特点: 设计用于单线程环境。在没有外部同步机制(如 lock)的情况下,从多个线程并发访问(特别是写入操作)是不安全的,可能导致数据竞争、状态不一致或异常。
  • 例子: List<T>, Dictionary<TKey, TValue>, HashSet<T>, ArrayList, Hashtable, Queue<T>, Stack<T> 等标准集合。

线程安全集合 (Thread-Safe Collections)

  • 特点: 设计用于在多线程环境中安全地进行并发访问。
  • 主要来源: System.Collections.Concurrent: 提供高效的线程安全集合实现。
  • 例子: ConcurrentDictionary<TKey, TValue>, ConcurrentQueue<T>, ConcurrentStack<T>, ConcurrentBag<T>, BlockingCollection<T>.
  • 同步包装器 (Legacy):ArrayList.Synchronized(),为非线程安全集合提供一个简单的锁包装,性能通常不如 Concurrent 集合。
  • 不可变集合: 因其不可变性,在多线程读取时是天然安全的。

按访问方式和结构分类

索引集合 (Indexed Collections)

  • 特点: 主要通过整数索引访问元素,通常保持元素顺序。
  • 例子: T[] (数组), List<T>, ArrayList, ObservableCollection<T>.

键控集合 (Keyed/Associative Collections)

  • 特点: 通过唯一的键来快速查找、添加、删除关联的值。
  • 例子: Dictionary<TKey, TValue>, Hashtable, ConcurrentDictionary<TKey, TValue>, SortedDictionary<TKey, TValue> (按键排序), SortedList<TKey, TValue> (按键排序)。

集合/集 (Set-Based Collections)

  • 特点: 存储唯一的元素,主要用于快速检查成员是否存在 (Contains) 和执行集合运算(并集、交集、差集等)。
  • 例子: HashSet<T>, SortedSet<T> (元素按顺序存储)。

顺序访问集合 (Sequential Access - FIFO/LIFO)

  • 特点: 元素的访问遵循特定顺序规则。
  • FIFO (先进先出): Queue<T>, ConcurrentQueue<T>.
  • LIFO (后进先出): Stack<T>, ConcurrentStack<T>.

按接口实现分类

集合类通常实现一个或多个接口,这些接口定义了它们的核心能力。了解这些接口有助于理解集合的功能契约:

  • IEnumerable/IEnumerable<T>: 支持使用 foreach 循环进行迭代。几乎所有集合都实现此接口。
  • ICollection/ICollection<T>: 提供基本的集合操作,如 Count, Add, Remove, Contains, CopyTo
  • IList/IList<T>: 支持通过索引访问元素,以及在特定位置插入和删除元素。
  • IDictionary/IDictionary<TKey, TValue>: 支持通过键访问值,管理键值对。
  • ISet<T>: 定义数学集合的操作,如并集、交集等。
  • IReadOnlyCollection<T>, IReadOnlyList<T>, IReadOnlyDictionary<TKey, TValue> 等: 提供对集合内容的只读访问视图。

常见集合

好的,我们来详细解析一下 C# 中 ArrayListHashSet<T>HashtableList<T>Dictionary<TKey, TValue> 这几个常用集合类的区别。

这些集合可以大致分为两类:

非泛型集合 (Non-Generic Collections)

位于 System.Collections 命名空间下,可以存储任意类型的对象(object)。

  1. ArrayList
  2. Hashtable

泛型集合 (Generic Collections)

位于 System.Collections.Generic 命名空间下,在创建时需要指定存储元素的具体类型,提供了类型安全和更好的性能。

  1. List<T>
  2. Dictionary<TKey, TValue>
  3. HashSet<T>
特性 ArrayList (非泛型) Hashtable (非泛型) List (泛型) Dictionary<TKey, TValue> (泛型) HashSet (泛型)
命名空间 System.Collections System.Collections System.Collections.Generic System.Collections.Generic System.Collections.Generic
类型安全 否 (存储 object) 否 (存储 object) 是 (存储指定类型 T) 是 (存储指定类型 TKey, TValue) 是 (存储指定类型 T)
存储内容 单个元素 键值对 (object, object) 单个元素 (T) 键值对 (TKey, TValue) 单个元素 (T)
内部结构 动态数组 哈希表 动态数组 哈希表 哈希表
是否有序 是 (按添加顺序) 否 (无序) 是 (按添加顺序) 否 (基本无序)* 否 (无序)
允许重复 Key 不允许, Value 允许 Key 不允许, Value 允许 否 (元素唯一)
访问方式 索引 (int) Key (object) 索引 (int) Key (TKey) N/A (主要靠 Contains)
性能 (增/删/查) 查(索引 O(1)), 增(摊销 O(1)), 删/包含(O(n)) 增/删/查(平均 O(1)) 查(索引 O(1)), 增(摊销 O(1)), 删/包含(O(n)) 增/删/查(平均 O(1)) 增/删/包含(平均 O(1))
主要用途 存储有序对象列表 (已过时) 存储键值对 (已过时) 存储类型安全的有序列表 存储类型安全的键值对 (快速查找) 存储唯一元素, 快速判断存在性
  • 注意: Dictionary 在较新的 .NET 版本中,其内部实现可能在迭代时 碰巧 保持插入顺序,但这并不是其设计保证的核心特性。不应依赖其顺序性。其核心优势在于基于 Key 的快速查找。

ArrayList (System.Collections)

描述: 一个动态数组,可以存储任何类型的对象。它会根据需要自动调整大小。

优点: 灵活,可以存储不同类型的对象。

缺点:

  1. 类型不安全: 由于存储的是 object,存入和取出时可能需要进行类型转换(拆箱/装箱),容易在运行时引发 InvalidCastException。
  2. 性能: 对于值类型,存入时会发生装箱(Boxing),取出时需要拆箱(Unboxing),这会带来性能开销。查找特定元素(非按索引)通常需要 O(n) 时间复杂度。

使用场景: 基本上已被 List 取代。只在需要与旧的、不支持泛型的 API 交互,或者极少数确实需要存储完全不同类型对象的场景下(但通常有更好的设计模式)才可能使用。

替代者: List

Hashtable (System.Collections)

描述: 一个基于哈希表的集合,用于存储键值对 (object Key, object Value)。通过 Key 快速查找 Value。

优点: 查找、添加、删除操作的平均时间复杂度接近 O(1)(假设哈希冲突不严重)。

缺点:

  1. 类型不安全: Key 和 Value 都是 object,需要类型转换,有运行时错误风险。
  2. 性能: 同样存在值类型的装箱/拆箱问题。
  3. 无序: 元素的存储和迭代顺序是不确定的。
  4. 线程安全 (潜在问题): 旧版本的 Hashtable 提供了某种程度的线程安全(通过内部锁),但这可能导致性能瓶颈。现代并发编程推荐使用 System.Collections.Concurrent 中的集合。

使用场景: 已被 Dictionary<TKey, TValue> 取代。理由同 ArrayList。

替代者: Dictionary<TKey, TValue>

List (System.Collections.Generic)

描述 : 泛型版本的 ArrayList。在创建时必须指定要存储的元素类型 T

优点:

  1. 类型安全: 编译器会在编译时检查类型,避免了运行时的类型转换错误。
  2. 性能 : 对于值类型,避免了装箱/拆箱的开销,性能通常优于 ArrayList
  3. 有序: 保持元素的插入顺序。
  4. 索引访问: 可以通过整数索引快速访问元素 (O(1))。

缺点:

  1. 查找/包含: Contains() 方法和按值删除 Remove() 通常需要遍历列表,时间复杂度为 O(n)。
  2. 插入/删除 (中间): 在列表的开头或中间插入/删除元素需要移动后续元素,时间复杂度为 O(n)。在末尾添加通常是摊销 O(1)。

使用场景: 最常用的集合之一。当你需要一个有序的、可通过索引访问的、类型安全的元素列表时使用。

Dictionary<TKey, TValue> (System.Collections.Generic)

描述: 泛型版本的 Hashtable。存储类型安全的键值对 (TKey, TValue)。

优点:

  1. 类型安全: 编译时进行类型检查。
  2. 性能: 避免了装箱/拆箱。基于 Key 的查找、添加、删除操作平均时间复杂度接近 O(1)。
  3. Key 唯一: 保证了 Key 的唯一性。

缺点:

  1. 无序: 通常不保证元素的顺序(虽然新版本实现可能有顺序性,但不应依赖)。
  2. 内存 : 相较于 List<T>,可能占用稍多内存,因为它需要存储哈希表结构。

使用场景: 当你需要根据一个唯一的标识符(Key)来快速查找、添加或删除一个关联的值(Value)时使用。例如,用用户 ID 查找用户信息。

HashSet (System.Collections.Generic)

描述: 一个包含不重复元素的无序集合。基于哈希表实现。

优点:

  1. 唯一性: 自动处理重复元素,集合中只包含唯一的元素。
  2. 高性能查找 : Contains() 方法(检查元素是否存在)、Add()Remove() 的平均时间复杂度接近 O(1)。
  3. 集合运算: 提供了高效的集合操作,如并集 (Union)、交集 (Intersect)、差集 (Except) 等。
  4. 类型安全: 泛型保证类型安全。

缺点:

  1. 无序: 不保证元素的任何特定顺序。
  2. 无索引访问: 不能通过索引访问元素。

使用场景: 当你需要存储一组唯一的元素,并且主要关心的是快速判断某个元素是否存在,或者进行集合运算时使用。例如,去除列表中的重复项,或者检查一个元素是否已处理过。

总结与建议

  • 优先选择泛型集合 : 在现代 C# 开发中,应始终优先考虑使用 System.Collections.Generic 命名空间下的泛型集合 (List<T>, Dictionary<TKey, TValue>, HashSet<T> 等)。它们提供了类型安全,避免了装箱/拆箱的性能损耗,并且在编译时就能发现类型错误。

根据需求选择:

  • 需要有序列表 ,允许重复,按索引 访问:使用 List<T>
  • 需要根据唯一 Key 快速查找 Value (键值对):使用 Dictionary<TKey, TValue>
  • 需要存储唯一元素 ,快速判断元素是否存在 ,或进行集合运算 :使用 HashSet<T>
  • 避免使用非泛型集合 : 除非是维护旧代码或与不支持泛型的旧库交互,否则尽量避免使用 ArrayListHashtable
相关推荐
艾上编程8 小时前
第四章——桌面小程序场景之使用Tkinter制作文件格式转换器:满足日常格式转换需求
开发语言·小程序
后端小张8 小时前
【JAVA 进阶】深入拆解SpringBoot自动配置:从原理到实战的完整指南
java·开发语言·spring boot·后端·spring·spring cloud·springboot
百锦再8 小时前
Kubernetes与开发语言:重新定义.NET Core与Java的云原生未来
开发语言·云原生·kubernetes
草莓熊Lotso8 小时前
C++11 核心进阶:引用折叠、完美转发与可变参数模板实战
开发语言·c++·人工智能·经验分享·后端·visualstudio·gitee
唐青枫8 小时前
C#.NET struct 全解析:什么时候该用值类型?
c#·.net
你不是我我8 小时前
【Java 开发日记】我们来说一下消息的可靠性投递
java·开发语言
电商API_180079052478 小时前
主流电商平台 API 横向测评:淘宝、京东、拼多多接口能力与对接成本分析
大数据·开发语言·网络·数据库·人工智能
free-elcmacom8 小时前
Python实战项目<3>赛制分数分析
开发语言·前端·python·数据分析
赵谨言8 小时前
基于OpenCV的数字识别系统
大数据·开发语言·经验分享·python