.NET Core(现统称为 .NET)提供了丰富的数据结构和特殊类型,旨在优化性能、内存管理以及开发效率。
常用集合与数据结构
线性结构
Array (T\[\])
特点:
- 内存连续分配,元素类型固定,长度不可变。
- 支持索引快速访问,读取速度极快。
- 增删元素效率低(需移动后续元素),长度固定。
数组是固定大小的连续内存块。不支持动态增删,只能修改元素或创建新数组。
cs
int[] arr = new int[5] { 1, 2, 3, 4, 5 };
// 【查】通过索引访问 O(1)
int val = arr[0];
// 【改】直接赋值
arr[0] = 10;
// 【增/删】数组本身不支持。需借助 Array.Resize 或 LINQ 创建新数组
// 模拟"添加":创建更大的数组并复制
Array.Resize(ref arr, arr.Length + 1);
arr[arr.Length - 1] = 6;
// 模拟"删除":通常使用 LINQ 过滤生成新数组
arr = arr.Where(x => x != 3).ToArray();
List
最常用的动态数组,支持索引访问。
特点:
- 动态数组,底层基于 Array 实现,但长度可变。
- 兼顾了读取速度(索引访问)和灵活性(自动扩容)。是日常开发中最常用的集合。
- 在列表中间插入或删除元素时,需要移动大量数据,性能较差。
cs
List<string> list = new List<string> { "A", "B", "C" };
// 【增】
list.Add("D"); // 尾部添加 O(1)
list.Insert(0, "Start"); // 指定位置插入 O(n)
// 【查】
string item = list[0]; // 索引访问 O(1)
bool exists = list.Contains("B"); // 线性查找 O(n)
string found = list.Find(x => x.StartsWith("A")); // 条件查找
// 【改】
list[0] = "Modified"; // 索引赋值
// 若元素是引用类型,可直接修改属性: list[0].Name = "New";
// 【删】
list.Remove("B"); // 删除第一个匹配项 O(n)
list.RemoveAt(0); // 按索引删除 O(n)
list.RemoveAll(x => x.Length < 2); // 批量删除
ArrayList
存储 object 类型,存在装箱/拆箱性能损耗,无类型安全。
特点:
- 非泛型集合,元素被视为 object。
- 存储值类型时会发生装箱/拆箱操作,影响性能且类型不安全。建议优先使用 List<T>。
cs
using System.Collections;
ArrayList arrayList = new ArrayList();
// 【增】
arrayList.Add(1); // 装箱
arrayList.Add("Text");
// 【查】
int first = (int)arrayList[0]; // 拆箱,需强制转换
// 【改】
arrayList[0] = 100;
// 【删】
arrayList.Remove(100);
arrayList.RemoveAt(0);
LinkedList
双向链表,适合频繁在中间插入/删除,不支持索引访问。
特点:
- 双向链表,内存不连续。
- 在任意位置插入和删除元素非常快(只需修改指针)。
- 不支持索引访问,遍历查找慢。
cs
LinkedList<int> linkedList = new LinkedList<int>(new[] { 1, 2, 3 });
// 【增】
linkedList.AddFirst(0);
linkedList.AddLast(4);
LinkedListNode<int> node = linkedList.Find(2);
if (node != null) linkedList.AddAfter(node, 5); // 在节点后插入 O(1)
// 【查】
// 不支持索引 linkedList[0] 是错误的
bool exists = linkedList.Contains(2); // O(n)
LinkedListNode<int> foundNode = linkedList.Find(2); // O(n)
// 【改】
if (foundNode != null) foundNode.Value = 20;
// 【删】
linkedList.Remove(20); // 按值删除 O(n)
linkedList.RemoveFirst(); // O(1)
linkedList.RemoveLast(); // O(1)
if (node != null) linkedList.Remove(node); // 按节点删除 O(1)
栈与队列
Stack
特点:后进先出(LIFO)结构。常用于表达式求值、撤销操作等场景。
cs
Stack<string> stack = new Stack<string>();
// 【增】
stack.Push("A");
stack.Push("B");
// 【查】仅能查看栈顶
string top = stack.Peek(); // 不移除
// 【删】仅能移除栈顶
string popped = stack.Pop();
// 注意:Stack 没有 Indexer,不能 stack[0],也不能直接修改中间元素
Queue
特点:先进先出(FIFO)结构。常用于任务排队、广度优先搜索等场景。
cs
Queue<string> queue = new Queue<string>();
// 【增】
queue.Enqueue("A");
queue.Enqueue("B");
// 【查】仅能查看队头
string head = queue.Peek();
// 【删】仅能移除队头
string dequeued = queue.Dequeue();
// 注意:Queue 同样不支持随机访问或修改中间元素
键值对与集合
Dictionary<TKey, TValue>
特点:
- 基于哈希表实现。
- 通过 Key 查找 Value 的速度极快(接近 O(1))。
- Key 必须唯一且不可变(或正确重写 GetHashCode 和 Equals)。
基于哈希表,Key 唯一。
cs
Dictionary<int, string> dict = new Dictionary<int, string>();
// 【增】
dict.Add(1, "One");
dict[2] = "Two"; // 若 Key 存在则覆盖,不存在则添加
// 【查】
string val = dict[1]; // 若 Key 不存在抛异常
bool hasKey = dict.ContainsKey(1); // 推荐先检查
bool tryGet = dict.TryGetValue(3, out string result); // 安全获取
// 【改】
dict[1] = "Updated One"; // 直接赋值覆盖
// 【删】
dict.Remove(1);
HashSet
基于哈希表,元素唯一,无序。
特点:
- 基于哈希表实现的集合,不包含重复元素。
- 快速判断元素是否存在,适合去重操作。
cs
HashSet<string> set = new HashSet<string>();
// 【增】
set.Add("A"); // 返回 bool,若已存在则返回 false 且不添加
// 【查】
bool exists = set.Contains("A"); // O(1)
// 【改】 HashSet 不支持直接"修改"元素。
// 若要修改,需先 Remove 再 Add
set.Remove("A");
set.Add("B");
// 【删】
set.Remove("A");
set.Clear();
SortedDictionary<TKey, TValue> / SortedSet
基于红黑树,Key/元素自动排序。
特点:
- 基于红黑树实现,元素自动排序。
- 遍历时有序,适合需要范围查询的场景。
cs
// SortedDictionary
SortedDictionary<int, string> sortedDict = new SortedDictionary<int, string>();
sortedDict.Add(3, "C");
sortedDict.Add(1, "A");
// 遍历按键排序: 1->A, 3->C
// 增删改查方法与 Dictionary 类似,但复杂度为 O(log n)
// SortedSet
SortedSet<int> sortedSet = new SortedSet<int>();
sortedSet.Add(5);
sortedSet.Add(1);
// 自动排序: 1, 5
// 支持范围操作
var subset = sortedSet.GetViewBetween(1, 10);
ConcurrentDictionary<TKey, TValue>
线程安全字典,适用于多线程。
特点:线程安全的字典,适用于多线程并发环境。
cs
using System.Collections.Concurrent;
ConcurrentDictionary<int, string> concDict = new ConcurrentDictionary<int, string>();
// 【增/改】原子操作
concDict.TryAdd(1, "A"); // 仅当 Key 不存在时添加
concDict[1] = "B"; // 直接赋值(线程安全但不保证原子性逻辑)
concDict.AddOrUpdate(1, "New", (key, oldVal) => "Updated"); // 原子性添加或更新
// 【查】
bool success = concDict.TryGetValue(1, out string val);
// 【删】
concDict.TryRemove(1, out string removedVal);
高性能与特殊类型
Span<T> 和 Memory<T> 主要用于切片和读取,它们不提供传统集合的 Add/Remove 方法,因为底层内存大小通常是固定的或受管理的。
为了提升系统性能和内存效率,.NET Core 引入了一些特殊的类型和结构:
内存优化类型
Span
栈上分配,高性能切片。
特点:
- 表示连续内存区域的任意切片。它是一个 ref struct(引用结构体),存储在栈上。
- 允许在不分配新内存的情况下,安全地操作数组、字符串或原生内存的一部分。
- 高性能字符串处理、网络包解析、大数据集切片。它避免了不必要的内存分配和复制,显著减少 GC 压力。
cs
int[] array = { 1, 2, 3, 4, 5 };
Span<int> span = array.AsSpan();
// 【查】
int first = span[0];
Span<int> subSpan = span.Slice(1, 2); // 获取子视图 [2, 3]
// 【改】
span[0] = 10; // 直接修改底层数组
// 【增/删】不支持。Span 长度固定。
// 若需"扩容",必须分配新的数组或 Span。
Memory
堆上分配,可用于异步场景。
特点:
- 与 Span<T> 类似,但可以存储在堆上(例如作为类字段或异步方法中的局部变量)。
- 当需要在异步操作中持有内存引用时使用。
cs
Memory<int> memory = new Memory<int>(new int[] { 1, 2, 3 });
// 【查】
int val = memory.Span[0]; // 通过 .Span 属性访问
// 【改】
memory.Span[0] = 10;
// 【增/删】不支持。同 Span,需重新分配内存。
基本数据类型
如 Int32 (int), Byte, Char, Boolean 等,均对应 CLR 中的值类型,存储在栈上(除非被装箱或作为对象字段)。
其他类型
String
虽然是引用类型,但在 .NET 中具有不可变性(Immutable)和驻留池(Interning)机制。
Nullable (T)
允许值类型(如 int, bool)拥有 null 值,增强了数据表达的灵活性。
其他特殊结构
Tuple / ValueTuple
用于轻量级地组合多个数据项,无需定义专门的类。ValueTuple 是值类型,性能更优。
cs
// ValueTuple (推荐,值类型,性能高)
var person = (Id: 1, Name: "Alice");
// 【改】无法直接修改字段,需重新赋值
person = person with { Name = "Bob" }; // C# 10+ 支持 with 表达式用于元组
// 或者
person = (1, "Bob");
// 【查】
string name = person.Name;
Record(C9+)
一种特殊的引用类型(或结构体记录),主要用于不可变数据模型,自动生成相等性比较逻辑,适合 DTO(数据传输对象)。
cs
public record Person(int Id, string Name);
var p1 = new Person(1, "Alice");
// 【改】使用 with 表达式创建修改后的副本(非破坏性更新)
var p2 = p1 with { Name = "Bob" };
// 【查】
Console.WriteLine(p2.Name);
// 【增/删】不适用,Record 是单个对象,不是集合。
接口
IEnumerable
所有集合的基础,支持 foreach 迭代(迭代器模式)。
只读,仅支持遍历。
cs
IEnumerable<int> enums = new List<int> { 1, 2, 3 };
// 【查】仅支持 foreach 或 LINQ
foreach (var item in enums) { }
// 无 Add, Remove, Indexer
ICollection
继承自 IEnumerable,提供 Count、Add、Remove 等基本操作。
支持计数、添加、删除、清空。
cs
ICollection<string> coll = new List<string>();
coll.Add("A"); // 【增】
coll.Remove("A"); // 【删】
coll.Clear(); // 【删所有】
int count = coll.Count; // 【查数量】
// 不支持索引访问 coll[0] 是错误的
IList
继承自 ICollection,支持索引访问(如 List)。
cs
IList<string> listInterface = new List<string>();
listInterface.Add("A"); // 【增】
listInterface[0] = "B"; // 【改/查】索引器
listInterface.RemoveAt(0); // 【删】
// 包含 Insert, IndexOf 等方法
IDictionary<TKey, TValue>
键值对集合的标准接口。
cs
IDictionary<int, string> dictInterface = new Dictionary<int, string>();
dictInterface.Add(1, "A"); // 【增】
dictInterface[1] = "B"; // 【改】
string val = dictInterface[1]; // 【查】
dictInterface.Remove(1); // 【删】
总结
- 日常开发:首选 List<T> 和 Dictionary<TKey, TValue>。
- 高性能场景:涉及大量字符串或数组切片操作时,使用 Span<T> 和 Memory<T> 以减少内存分配。
- 并发场景:使用 ConcurrentDictionary 或 ConcurrentQueue 等线程安全集合。
- 避免使用:尽量避免使用非泛型的 ArrayList 和 Hashtable,除非为了兼容极旧的代码。
| 需求场景 | 推荐类型 | 关键方法 |
|---|---|---|
| 通用动态列表 | List<T> |
Add, RemoveAt, [i] |
| 快速键值查找 | Dictionary<K,V> |
TryGetValue, Add, Remove |
| 去重集合 | HashSet<T> |
Add, Contains, Remove |
| 有序键值对 | SortedDictionary<K,V> |
同 Dictionary,自动排序 |
| 多线程共享 | ConcurrentDictionary |
TryAdd, TryRemove, AddOrUpdate |
| 高性能切片/解析 | Span<T> / Memory<T> |
Slice, [i] (只读/修改,无增删) |
| 先进先出队列 | Queue<T> |
Enqueue, Dequeue, Peek |
| 后进先出栈 | Stack<T> |
Push, Pop, Peek |