news 2026/8/1 13:15:43

C# 进阶知识点串讲:从集合到多线程的实用指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C# 进阶知识点串讲:从集合到多线程的实用指南

一、集合与泛型:告别类型不安全的时代

1.1 从ArrayList到List<T>:一次质的飞跃

在C# 2.0之前,ArrayList是处理动态集合的主要方式。它就像一个“万能容器”,什么类型的数据都能往里塞:

这种灵活性背后隐藏着巨大的代价。ArrayList的本质是一个object类型的数组——所有元素都被当作object来存储。当你存入一个值类型(如int)时,它会被装箱(从值类型转换为引用类型);取出时又需要拆箱(从引用类型转换回值类型)。

装箱和拆箱为什么是性能杀手?每次装箱都会在托管堆上分配新的内存,频繁操作会带来可观的内存压力和CPU开销。更糟糕的是,类型安全问题——如果你不小心把字符串当作整数来拆箱,程序会在运行时抛出InvalidCastException

List<T>的出现彻底改变了这一局面。作为泛型集合,它在编译时就确定了元素类型:

泛型的核心优势有三点

优势说明
类型安全编译时检查类型,错误提前暴露
性能卓越值类型无需装箱拆箱,直接存储
代码复用一套代码适用于多种数据类型

.NET的泛型在运行时是“保留类型信息”的——List<int>List<string>在运行时是两个不同的类型,各自有独立的实现。这种设计既保证了类型安全,又实现了高性能。

实践建议:在现代C#开发中,ArrayList已被官方标记为“仅用于兼容性目的”。动态集合场景请始终使用List<T>

1.2 Queue与Stack:两种经典的数据结构

如果说List<T>是一个可以随意存取元素的“仓库”,那么Queue<T>Stack<T>就是有着严格存取规则的“专用通道”。

Queue(队列)遵循先进先出(FIFO)原则。就像超市收银台前排队的顾客——先来的人先结账,后来的人排在队尾。在C#中,使用Enqueue入队,Dequeue出队:

Stack(栈)则遵循后进先出(LIFO)原则。想象一摞盘子——你总是先取走最上面那个最后放上去的盘子。使用Push入栈,Pop出栈:

什么时候用哪个?需要按到达顺序处理任务时用队列(如消息队列、请求排队);需要回溯撤销操作时用栈(如浏览器历史、函数调用栈)。

1.3 Dictionary与Hashtable:键值对的进化

HashtableDictionary<TKey, TValue>都是键值对集合,通过键来快速查找值。但它们之间的差异,与ArrayListList<T>如出一辙。

Hashtable是非泛型的——键和值都是object类型,同样存在装箱拆箱和类型安全问题。而Dictionary<TKey, TValue>是泛型版本,提供了类型安全和更好的性能。

Dictionary的查找速度非常快,时间复杂度接近O(1)。它的典型应用场景包括:数据索引(用ID快速查记录)、统计频次(统计单词出现次数)、缓存数据等。

SortedList则是在Dictionary的基础上增加了按键排序的功能。它既可以通过键访问,也可以通过索引访问,元素始终按照键的顺序排列。


二、异常处理:让程序优雅地倒下

2.1 异常的本质

异常是程序运行过程中发生的“意外状况”——比如访问了空对象的成员(NullReferenceException)、数组索引越界(IndexOutOfRangeException)、除以零(DivideByZeroException)等。

在C#中,异常是对象——所有异常都派生自System.Exception类。当问题发生时,系统会创建异常对象并“抛出”(throw),包含错误位置、原因等诊断信息。

2.2 try-catch-finally 的正确姿势

异常处理的核心是try-catch-finally结构:

关键原则

  1. catch块按从具体到通用的顺序排列——先捕获DivideByZeroException,最后才是Exception

  2. 只在能恢复的情况下捕获异常——如果无法恢复,让异常向上传递。

  3. 使用finally释放资源——关闭文件、数据库连接等。

2.3 throw vs throw ex:一个天壤之别

这是C#新手最容易踩的坑之一:

使用throw ex重置异常的堆栈跟踪信息,让你找不到真正的错误源头。正确的做法是:

或者,如果你需要添加额外的上下文信息:

总结:永远使用throw;而不是throw ex;

2.4 using语句:自动的资源管理

using语句是C#的“语法糖”——它本质上是一个try-finally块,在finally中自动调用对象的Dispose()方法释放资源:

任何实现了IDisposable接口的类型都可以使用using语句——这包括了文件流、数据库连接、网络连接等几乎所有涉及非托管资源的类型。


三、IO与文件操作:让数据流动起来

3.1 流的哲学

在.NET中,流(Stream)是数据流动的抽象概念。它就像一根水管——数据从一端流入,从另一端流出。所有的流操作都围绕三个基本动作展开:读取(从流到内存)、写入(从内存到流)和查找(移动当前位置)。

3.2 FileStream:字节级别的文件操作

FileStream是最底层的文件流,操作的是字节。它适用于任何类型的文件——文本、图片、视频、音频等。

FileMode枚举控制文件的打开方式:

FileMode行为
Create创建新文件,若存在则覆盖
CreateNew创建新文件,若存在则抛出异常
Open打开现有文件
OpenOrCreate存在则打开,不存在则创建
Append打开并定位到文件末尾(追加)

3.3 StreamReader / StreamWriter:文本的便捷读写

如果你处理的是文本文件StreamReaderStreamWriter是更便捷的选择——它们直接操作字符,自动处理编码转换:

选择指南:处理二进制文件(图片、视频)用FileStream;处理文本文件用StreamReader/StreamWriter;需要精细控制(如加密、压缩)时用CryptoStreamGZipStream等装饰器流。

3.4 File与FileInfo:静态与实例的取舍

File类是静态类,提供了一系列静态方法进行文件操作:

FileInfo实例类,需要创建对象后调用实例方法:

如何选择?单次操作用File类更简洁;多次操作同一个文件用FileInfo更高效(每次File的静态方法都会进行安全检查,而FileInfo只检查一次)。同理,DirectoryDirectoryInfo也遵循同样的取舍原则。


四、多线程与同步:让程序并行起来

4.1 进程与线程:工厂与工人的比喻

理解多线程,先理解两个核心概念:

  • 进程就像一座工厂的车间——每个车间独立运行,有自己的资源和空间。

  • 线程就像车间里的工人——同一个车间的工人共享空间(内存),协同完成任务。

CPU就像工厂的电力供应,同一时刻只能为一个车间(进程)供电。操作系统通过时间片轮转让多个进程“看起来”在同时运行。

4.2 Thread:最基础的线程操作

在C#中,Thread类是最基础的线程操作方式:

线程优先级:通过Priority属性可以设置LowestHighest五个等级。高优先级的线程获得更多CPU时间,但不能滥用——优先级过高可能导致低优先级线程“饿死”。

前台线程 vs 后台线程:前台线程会阻止程序退出,而后台线程不会。将线程的IsBackground设为true使其成为后台线程。

4.3 lock与Mutex:同步的艺术

当多个线程同时访问共享资源时,竞态条件(race condition)就会出现。比如两个线程同时修改同一个变量,结果可能完全错误。

lock是最常用的同步机制,它确保同一时刻只有一个线程能进入被锁定的代码块:

lock本质上是Monitor的语法糖,编译器会自动生成try-finally来确保锁被释放。

Mutex(互斥体)则更强大——它支持跨进程同步

lock与Mutex的选择

特性lockMutex
适用范围同一进程内的线程可跨进程
性能轻量级,开销小重量级,涉及系统调用
使用复杂度简单相对复杂
递归锁支持不支持(同一线程重复锁定会死锁)支持

实践建议:绝大多数情况下使用lock就足够了。只有在需要跨多个进程同步时,才考虑Mutex

4.4 死锁:多线程的最大敌人

死锁(Deadlock)是指两个或多个线程互相等待对方释放资源,结果谁也无法继续执行。

典型的死锁场景:线程A持有资源1,等待资源2;线程B持有资源2,等待资源1。两者互不相让,永久阻塞。

预防死锁的原则

  1. 固定锁的顺序——所有线程按相同的顺序获取锁

  2. 尽量缩短锁定时间——只在必要的时候才持有锁

  3. 使用超时机制——Monitor.TryEnter可以设置等待超时


写在最后

ArrayListList<T>,从HashtableDictionary,C#的进化始终围绕着类型安全性能这两个核心目标。泛型让代码既安全又高效;异常处理让程序健壮可控;流和文件操作让数据自由流动;多线程让程序充分利用硬件资源。

记住几个关键原则

  1. 能用泛型就别用非泛型——类型安全和性能都更好

  2. 能用using就别手动Dispose——简洁且安全

  3. 能用throw;就别用throw ex;——保留完整的错误信息

  4. 能用lock就别用Mutex——除非你真的需要跨进程同步

掌握这些核心特性,C#代码将更加健壮、高效、可维护。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/1 13:15:13

Java并发编程:AtomicBoolean原理、应用场景与性能优化指南

1. 从“锁”到“原子”&#xff1a;为什么我们需要 AtomicBoolean在并发编程的世界里&#xff0c;共享变量的读写就像一条繁忙的单车道&#xff0c;如果不对车辆&#xff08;线程&#xff09;进行协调&#xff0c;撞车&#xff08;数据不一致&#xff09;是迟早的事。传统上&am…

作者头像 李华
网站建设 2026/8/1 13:13:52

AI表格复制技术解析与应用场景

1. AI表格复制操作全解析 在数据处理和办公自动化领域&#xff0c;AI表格操作已经成为提升效率的利器。作为每天与Excel、Google Sheets打交道的从业者&#xff0c;我发现AI赋能的表格处理可以节省大量重复劳动时间。以最常见的复制操作为例&#xff0c;传统方式需要手动选择区…

作者头像 李华
网站建设 2026/8/1 13:09:21

不再手写 Prompt 链:用工作流编译器构建可验证的多模型 Agent 运行时

许多 Agent 项目从一段看似清晰的业务代码开始&#xff1a;先让大模型拆解任务&#xff0c;再根据返回文本调用搜索、图片或视频接口&#xff0c;最后把所有结果交给另一个模型汇总。原型阶段只有三五个节点&#xff0c;这种写法足够直接&#xff1b;进入生产环境后&#xff0c…

作者头像 李华
网站建设 2026/8/1 13:07:23

【回眸】Memmy Agent 智能体落地应用全景指南

在日常的业务开发中&#xff0c;我们常常面临这样的困境&#xff1a;重复性的高频操作占据了团队大量精力&#xff0c;而真正需要创造性思维的工作却被挤压得所剩无几。无论是电商大促期间如潮水般的咨询消息&#xff0c;还是跨国业务中繁琐的语言转换&#xff0c;亦或是内部知…

作者头像 李华
网站建设 2026/8/1 13:07:07

九大网盘直链下载助手:告别客户端,浏览器直接获取下载地址

九大网盘直链下载助手&#xff1a;告别客户端&#xff0c;浏览器直接获取下载地址 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 &#xff0c;支持 百度网盘 / 阿里云盘 / 中国…

作者头像 李华