文章目录
- 为什么需要集合
- 核心操作
- **主要命名空间**
- [**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#(以及许多其他编程语言)中,集合 是一个用来存储和管理一组其他对象(称为元素或项)的容器对象。
简单来说,当你需要处理多个相关的数据项时,将它们放入一个集合中通常比为每个项创建单独的变量更方便、更有效。想象一下你需要存储一个班级所有学生的名字,或者一个购物车里所有商品的列表,使用集合就非常合适。
为什么需要集合
- 数据分组: 将逻辑上相关的项组织在一起。
- 动态大小: 大多数集合类型的大小不是固定的(与数组
T[]不同),它们可以根据需要动态地增加或减少容量来容纳更多或更少的元素。 - 高效操作: 集合提供了优化过的方法来执行常见操作,如添加 (Add)、移除 (Remove)、查找 (Find/Contains)、排序 (Sort) 等。其效率取决于集合的具体类型及其底层数据结构。
- 统一管理: 通过集合对象统一管理一组数据,简化代码逻辑。
- 迭代: 提供标准的机制(如
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# 中 ArrayList、HashSet<T>、Hashtable、List<T> 和 Dictionary<TKey, TValue> 这几个常用集合类的区别。
这些集合可以大致分为两类:
非泛型集合 (Non-Generic Collections)
位于 System.Collections 命名空间下,可以存储任意类型的对象(object)。
ArrayListHashtable
泛型集合 (Generic Collections)
位于 System.Collections.Generic 命名空间下,在创建时需要指定存储元素的具体类型,提供了类型安全和更好的性能。
List<T>Dictionary<TKey, TValue>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)
描述: 一个动态数组,可以存储任何类型的对象。它会根据需要自动调整大小。
优点: 灵活,可以存储不同类型的对象。
缺点:
- 类型不安全: 由于存储的是 object,存入和取出时可能需要进行类型转换(拆箱/装箱),容易在运行时引发 InvalidCastException。
- 性能: 对于值类型,存入时会发生装箱(Boxing),取出时需要拆箱(Unboxing),这会带来性能开销。查找特定元素(非按索引)通常需要 O(n) 时间复杂度。
使用场景: 基本上已被 List 取代。只在需要与旧的、不支持泛型的 API 交互,或者极少数确实需要存储完全不同类型对象的场景下(但通常有更好的设计模式)才可能使用。
替代者: List
Hashtable (System.Collections)
描述: 一个基于哈希表的集合,用于存储键值对 (object Key, object Value)。通过 Key 快速查找 Value。
优点: 查找、添加、删除操作的平均时间复杂度接近 O(1)(假设哈希冲突不严重)。
缺点:
- 类型不安全: Key 和 Value 都是 object,需要类型转换,有运行时错误风险。
- 性能: 同样存在值类型的装箱/拆箱问题。
- 无序: 元素的存储和迭代顺序是不确定的。
- 线程安全 (潜在问题): 旧版本的 Hashtable 提供了某种程度的线程安全(通过内部锁),但这可能导致性能瓶颈。现代并发编程推荐使用 System.Collections.Concurrent 中的集合。
使用场景: 已被 Dictionary<TKey, TValue> 取代。理由同 ArrayList。
替代者: Dictionary<TKey, TValue>
List (System.Collections.Generic)
描述 : 泛型版本的 ArrayList。在创建时必须指定要存储的元素类型 T。
优点:
- 类型安全: 编译器会在编译时检查类型,避免了运行时的类型转换错误。
- 性能 : 对于值类型,避免了装箱/拆箱的开销,性能通常优于
ArrayList。 - 有序: 保持元素的插入顺序。
- 索引访问: 可以通过整数索引快速访问元素 (O(1))。
缺点:
- 查找/包含: Contains() 方法和按值删除 Remove() 通常需要遍历列表,时间复杂度为 O(n)。
- 插入/删除 (中间): 在列表的开头或中间插入/删除元素需要移动后续元素,时间复杂度为 O(n)。在末尾添加通常是摊销 O(1)。
使用场景: 最常用的集合之一。当你需要一个有序的、可通过索引访问的、类型安全的元素列表时使用。
Dictionary<TKey, TValue> (System.Collections.Generic)
描述: 泛型版本的 Hashtable。存储类型安全的键值对 (TKey, TValue)。
优点:
- 类型安全: 编译时进行类型检查。
- 性能: 避免了装箱/拆箱。基于 Key 的查找、添加、删除操作平均时间复杂度接近 O(1)。
- Key 唯一: 保证了 Key 的唯一性。
缺点:
- 无序: 通常不保证元素的顺序(虽然新版本实现可能有顺序性,但不应依赖)。
- 内存 : 相较于
List<T>,可能占用稍多内存,因为它需要存储哈希表结构。
使用场景: 当你需要根据一个唯一的标识符(Key)来快速查找、添加或删除一个关联的值(Value)时使用。例如,用用户 ID 查找用户信息。
HashSet (System.Collections.Generic)
描述: 一个包含不重复元素的无序集合。基于哈希表实现。
优点:
- 唯一性: 自动处理重复元素,集合中只包含唯一的元素。
- 高性能查找 :
Contains()方法(检查元素是否存在)、Add()、Remove()的平均时间复杂度接近 O(1)。 - 集合运算: 提供了高效的集合操作,如并集 (Union)、交集 (Intersect)、差集 (Except) 等。
- 类型安全: 泛型保证类型安全。
缺点:
- 无序: 不保证元素的任何特定顺序。
- 无索引访问: 不能通过索引访问元素。
使用场景: 当你需要存储一组唯一的元素,并且主要关心的是快速判断某个元素是否存在,或者进行集合运算时使用。例如,去除列表中的重复项,或者检查一个元素是否已处理过。
总结与建议
- 优先选择泛型集合 : 在现代 C# 开发中,应始终优先考虑使用
System.Collections.Generic命名空间下的泛型集合 (List<T>,Dictionary<TKey, TValue>,HashSet<T>等)。它们提供了类型安全,避免了装箱/拆箱的性能损耗,并且在编译时就能发现类型错误。
根据需求选择:
- 需要有序列表 ,允许重复,按索引 访问:使用
List<T>。 - 需要根据唯一 Key 快速查找 Value (键值对):使用
Dictionary<TKey, TValue>。 - 需要存储唯一元素 ,快速判断元素是否存在 ,或进行集合运算 :使用
HashSet<T>。 - 避免使用非泛型集合 : 除非是维护旧代码或与不支持泛型的旧库交互,否则尽量避免使用
ArrayList和Hashtable。