跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

C#怎么使用Stack栈_C#如何处理后进先出的逻辑顺序【代码】

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

相关文章