一、集合与泛型告别类型不安全的时代1.1 从ArrayList到ListT一次质的飞跃在C# 2.0之前ArrayList是处理动态集合的主要方式。它就像一个“万能容器”什么类型的数据都能往里塞这种灵活性背后隐藏着巨大的代价。ArrayList的本质是一个object类型的数组——所有元素都被当作object来存储。当你存入一个值类型如int时它会被装箱从值类型转换为引用类型取出时又需要拆箱从引用类型转换回值类型。装箱和拆箱为什么是性能杀手每次装箱都会在托管堆上分配新的内存频繁操作会带来可观的内存压力和CPU开销。更糟糕的是类型安全问题——如果你不小心把字符串当作整数来拆箱程序会在运行时抛出InvalidCastException。ListT的出现彻底改变了这一局面。作为泛型集合它在编译时就确定了元素类型泛型的核心优势有三点优势说明类型安全编译时检查类型错误提前暴露性能卓越值类型无需装箱拆箱直接存储代码复用一套代码适用于多种数据类型.NET的泛型在运行时是“保留类型信息”的——Listint和Liststring在运行时是两个不同的类型各自有独立的实现。这种设计既保证了类型安全又实现了高性能。实践建议在现代C#开发中ArrayList已被官方标记为“仅用于兼容性目的”。动态集合场景请始终使用ListT。1.2 Queue与Stack两种经典的数据结构如果说ListT是一个可以随意存取元素的“仓库”那么QueueT和StackT就是有着严格存取规则的“专用通道”。Queue队列遵循先进先出FIFO原则。就像超市收银台前排队的顾客——先来的人先结账后来的人排在队尾。在C#中使用Enqueue入队Dequeue出队Stack栈则遵循后进先出LIFO原则。想象一摞盘子——你总是先取走最上面那个最后放上去的盘子。使用Push入栈Pop出栈什么时候用哪个需要按到达顺序处理任务时用队列如消息队列、请求排队需要回溯或撤销操作时用栈如浏览器历史、函数调用栈。1.3 Dictionary与Hashtable键值对的进化Hashtable和DictionaryTKey, TValue都是键值对集合通过键来快速查找值。但它们之间的差异与ArrayList和ListT如出一辙。Hashtable是非泛型的——键和值都是object类型同样存在装箱拆箱和类型安全问题。而DictionaryTKey, TValue是泛型版本提供了类型安全和更好的性能。Dictionary的查找速度非常快时间复杂度接近O(1)。它的典型应用场景包括数据索引用ID快速查记录、统计频次统计单词出现次数、缓存数据等。SortedList则是在Dictionary的基础上增加了按键排序的功能。它既可以通过键访问也可以通过索引访问元素始终按照键的顺序排列。二、异常处理让程序优雅地倒下2.1 异常的本质异常是程序运行过程中发生的“意外状况”——比如访问了空对象的成员NullReferenceException、数组索引越界IndexOutOfRangeException、除以零DivideByZeroException等。在C#中异常是对象——所有异常都派生自System.Exception类。当问题发生时系统会创建异常对象并“抛出”throw包含错误位置、原因等诊断信息。2.2 try-catch-finally 的正确姿势异常处理的核心是try-catch-finally结构关键原则catch块按从具体到通用的顺序排列——先捕获DivideByZeroException最后才是Exception。只在能恢复的情况下捕获异常——如果无法恢复让异常向上传递。使用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文本的便捷读写如果你处理的是文本文件StreamReader和StreamWriter是更便捷的选择——它们直接操作字符自动处理编码转换选择指南处理二进制文件图片、视频用FileStream处理文本文件用StreamReader/StreamWriter需要精细控制如加密、压缩时用CryptoStream、GZipStream等装饰器流。3.4 File与FileInfo静态与实例的取舍File类是静态类提供了一系列静态方法进行文件操作FileInfo是实例类需要创建对象后调用实例方法如何选择单次操作用File类更简洁多次操作同一个文件用FileInfo更高效每次File的静态方法都会进行安全检查而FileInfo只检查一次。同理Directory与DirectoryInfo也遵循同样的取舍原则。四、多线程与同步让程序并行起来4.1 进程与线程工厂与工人的比喻理解多线程先理解两个核心概念进程就像一座工厂的车间——每个车间独立运行有自己的资源和空间。线程就像车间里的工人——同一个车间的工人共享空间内存协同完成任务。CPU就像工厂的电力供应同一时刻只能为一个车间进程供电。操作系统通过时间片轮转让多个进程“看起来”在同时运行。4.2 Thread最基础的线程操作在C#中Thread类是最基础的线程操作方式线程优先级通过Priority属性可以设置Lowest到Highest五个等级。高优先级的线程获得更多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。两者互不相让永久阻塞。预防死锁的原则固定锁的顺序——所有线程按相同的顺序获取锁尽量缩短锁定时间——只在必要的时候才持有锁使用超时机制——Monitor.TryEnter可以设置等待超时写在最后从ArrayList到ListT从Hashtable到DictionaryC#的进化始终围绕着类型安全和性能这两个核心目标。泛型让代码既安全又高效异常处理让程序健壮可控流和文件操作让数据自由流动多线程让程序充分利用硬件资源。记住几个关键原则能用泛型就别用非泛型——类型安全和性能都更好能用using就别手动Dispose——简洁且安全能用throw;就别用throw ex;——保留完整的错误信息能用lock就别用Mutex——除非你真的需要跨进程同步掌握这些核心特性C#代码将更加健壮、高效、可维护。