Stack 应直接用 new Stack() 初始化,Push/Pop/Peek 操作需检查 Count 避免空栈异常;遍历时禁用 foreach 中 Pop,改用 while;多线程须用 ConcurrentStack 而非加锁。
Stack
的基本创建和压入/弹出操作
直接用
就能初始化,别手贱去继承它或自己写数组模拟——C# 的
已经是泛型、线程不安全但高性能的成熟实现。压入用
,弹出用
,查顶元素用
(不移除)。
常见错误:调用
前没检查
,直接抛
。这不是设计缺陷,是明确告诉你“你忘了兜底”。
不要用
存不同类型的值再手动转型——类型擦除后运行时容易崩,改用具体泛型类型或
(仅限极少数动态场景)
时间复杂度是 O(1),但底层扩容时会触发数组复制,频繁 Push 小量数据无感;如果已知大概容量,构造时传入初始容量更稳:
遍历 Stack
时为什么不能边遍历边 Pop()
因为
的枚举器(foreach 所用)是“快照式”的——它按当前栈内元素从顶到底顺序返回副本,不反映后续
或
的变化。但如果你在 foreach 中调用
,实际是在修改原集合,.NET 会立刻抛
。
真实需求通常是“清空并处理每个元素”,这时别 foreach,改用 while:
C知道
CSDN推出的一款AI技术问答工具
下载
想保留原栈?先
或
拷贝一份再遍历(注意:
返回的是从底到顶顺序,和栈逻辑相反)
需要从底到顶遍历?别硬绕,转成
后
,或者直接用
Stack
和 List在 LIFO 场景下的取舍
表面上都能实现后进先出,但语义和性能差很远。
的 API 强制你只关注栈顶,
/
是唯一自然入口;而
要靠
+
模拟,代码啰嗦且易错(比如写成
就变成 FIFO 了)。
用
模拟栈:每次
是 O(n),因为要移动后面所有元素;
的
是稳定 O(1)
不支持随机索引访问(没有
),这是故意的——如果你频繁需要索引,说明它根本不是栈场景,该换数据结构
兼容性注意:.NET Framework 2.0+ 和所有 .NET Core/.NET 5+ 都有
,无需额外 NuGet
多线程环境下 Stack
的典型误用
默认**不是线程安全的**。两个线程同时
可能导致内部数组状态错乱,甚至出现
或静默丢数据——它不会加锁,也不会报“线程不安全”警告。
真要跨线程共享栈?用
,它的
/
是原子操作,且
返回 bool 表示是否成功,比捕异常更合理
别给
加
后当并发栈用——锁粒度大,吞吐低,还容易死锁;
内部用无锁算法,性能好得多
常见陷阱:把
放在静态字段里供多个请求共用,没加锁也没换并发版本,压测时偶发崩溃或逻辑错乱
栈不是万能容器,它只解决“最后一次操作最优先响应”这个具体问题。用错场景比用错语法更容易埋雷——比如用它存用户会话上下文却不考虑生命周期管理,最后栈越堆越深,内存涨得无声无息。
new Stack() StackPush()Pop()Peek()Pop()Count == 0InvalidOperationException: Stack is emptyStack s = new(); s.Push(1); s.Push(2); Console.WriteLine(s.Pop()); // 输出 2 StackStackPush()new Stack(1024) StackPop()Push()Pop()InvalidOperationException: Collection was modifiedwhile (s.Count > 0) { var item = s.Pop(); Process(item); }ToArray()ToList()ToArray()ListReverse()s.ToList().AsEnumerable().Reverse()StackPush()Pop()ListAdd()RemoveAt(list.Count - 1)RemoveAt(0)ListRemoveAt()StackPop()Stackthis[int]StackStackPush()NullReferenceExceptionConcurrentStackPush()TryPop()TryPop()StacklockConcurrentStackStack