C# 数据结构和特殊类型

.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
相关推荐
希和202719 分钟前
C语言程序设计题|有序数组插入数字冒泡排序
c语言·数据结构·算法
IT规划师1 小时前
Redis 数据类型全解 - Stream 深度:消费者组、Pending 队列与死信处理(十)
redis·.net
布莱克60511 小时前
C++ 中 map 的 insert 与 emplace:key 已存在时会覆盖吗?
开发语言·数据结构·c++·算法·map
D e re k ZH14 小时前
C++ 系统学习手记:从语法细节到工程实践
开发语言·数据结构·c++·算法
zh_xuan20 小时前
LeeCode 496. 下一个更大元素I
数据结构·算法·栈·单调栈
白杨尚青1 天前
C++进阶篇(五):红黑树——四条规则、三种插入情况与旋转变色
开发语言·数据结构·c++·stl·红黑树·平衡二叉树
Jeff-Tsai1 天前
Day 14:业务对象的扩展点与重写时机
.net·架构设计·erp·definition-driven
IT规划师1 天前
Redis 数据类型全解 - GEO 地理空间:附近的人、外卖、打车——背后原理与精度陷阱(九)
redis·.net
likexuec2 天前
数据结构(2)
数据结构