C# 从集合、泛型、异常到 IO、多线程全套入门笔记
摘要:本文整合 C# 基础进阶五大核心模块:非泛型 / 泛型集合、泛型原理与约束、异常处理机制、文件 IO 流操作、进程与多线程同步,配套完整可运行代码,区分新旧容器优劣,总结开发避坑点,适合零基础入门、面试突击复习。 标签:C#; .NET; 集合;泛型;多线程;IO 流;异常处理 分类:后端开发 / C# 桌面开发
前言
初学 C# 时很容易混淆ArrayList和List、分不清栈 / 队列使用场景、写文件忘记释放资源、多线程并发出现数据错乱。本文结合课堂全套笔记,把集合容器、泛型、异常、IO 文件、多线程五大高频知识点一次性梳理清楚,所有案例均可直接复制运行,同时标注生产环境推荐写法与淘汰 API。
一、集合容器:线性容器 & 键值容器
1.1 非泛型容器(System.Collections,新项目不推荐)
1.1.1 ArrayList 动态数组
本质是object[],可存任意类型,存在装箱拆箱损耗,无类型校验。
核心特性
- 容量自动扩容,默认初始容量 16;
- 支持
Add()尾部添加、Insert(index,obj)指定位置插入; - 删除:
Clear()清空、Remove(元素)删首个匹配、RemoveAt(索引)、RemoveRange(起始,数量); - 查询:
Contains()判断存在、IndexOf()获取索引; - 属性:
Count实际元素数 /Capacity总容量。
示例代码
csharp
运行
using System; using System.Collections; class TestArrayList { static void Main() { ArrayList arr = new ArrayList(); arr.Add(10); arr.Add("测试字符串"); arr.Add(true); arr.Insert(1, 999); // 索引1插入999 arr.Remove("测试字符串"); // 删除指定元素 arr.RemoveAt(0); // 删除索引0 Console.WriteLine(arr.Contains(true)); // true // 遍历 foreach (var item in arr) { Console.WriteLine(item); } } }缺点:混合类型存储,取值强制转换极易报错,性能差,新项目一律用
List<T>替代。
1.1.2 Queue 队列(FIFO 先进先出)
场景:消息队列、任务排队、打印队列。
- 入队:
Enqueue(obj) - 出队(取出并删除):
Dequeue() - 查看队首(不删除):
Peek() - 统计:
Count
csharp
运行
Queue queue = new Queue(); queue.Enqueue("消息1"); queue.Enqueue("消息2"); Console.WriteLine(queue.Peek()); // 消息1 Console.WriteLine(queue.Dequeue()); // 消息1,队列仅剩消息21.1.3 Stack 栈(LIFO 后进先出)
场景:撤销操作、递归回溯、表达式求值。
- 入栈:
Push(obj) - 出栈:
Pop() - 查看栈顶:
Peek()
csharp
运行
Stack stack = new Stack(); stack.Push("页面A"); stack.Push("页面B"); Console.WriteLine(stack.Pop()); // 页面B1.1.4 Hashtable 哈希键值对
键唯一,存储DictionaryEntry,同样 object 装箱,淘汰方案:Dictionary<TKey,TValue>
csharp
运行
Hashtable ht = new Hashtable(); ht.Add("id", 1001); ht["name"] = "张三"; // 赋值,键存在则覆盖 Console.WriteLine(ht.ContainsKey("id")); // 遍历键值对 foreach (DictionaryEntry kv in ht) { Console.WriteLine($"{kv.Key}:{kv.Value}"); }1.1.5 SortedList 自动排序键值容器
自动按照 key 升序,同时支持索引访问+键访问双模式;不允许重复 key。
csharp
运行
SortedList sl = new SortedList(); sl.Add(3, "C"); sl.Add(1, "A"); sl.Add(2, "B"); // 自动排序输出 1:A 2:B 3:C1.2 泛型容器(System.Collections.Generic,生产首选)
1.2.1 List<T> 泛型动态数组(替代 ArrayList)
强类型约束,无装箱拆箱,编译期类型检查,方法与 ArrayList 完全对齐。
csharp
运行
// 集合初始化器 List<int> numList = new List<int>() { 1,2,3,4 }; numList.Add(5); numList.Insert(0,0); // 存储自定义实体 class Student { public int Id { get; set; } public string Name { get; set; } } List<Student> stus = new List<Student>() { new Student{Id=1001,Name="小明"} };1.2.2 Dictionary<TKey,TValue> 泛型字典(替代 Hashtable)
核心优势:O (1) 键查找,推荐TryGetValue安全取值,避免键不存在抛异常。
csharp
运行
Dictionary<string,int> ageDic = new Dictionary<string,int>(); ageDic["小明"] = 18; ageDic.Add("小红",17); // 安全取值(推荐) if(ageDic.TryGetValue("小明",out int age)) { Console.WriteLine(age); } // 三种遍历 foreach(var kv in ageDic){} // 键值对 foreach(var k in ageDic.Keys){} // 仅键 foreach(var v in ageDic.Values){} // 仅值1.2.3 Queue<T> / Stack<T> 泛型栈队列
用法同非泛型,仅增加类型约束,杜绝类型混乱。
csharp
运行
Queue<string> msgQueue = new Queue<string>(); Stack<int> numStack = new Stack<int>();1.3 容器选型对照表
表格
| 容器 | 存储结构 | 特性 | 适用场景 | 推荐度 |
|---|---|---|---|---|
| ArrayList | object 数组 | 任意类型、装箱 | 老旧维护项目 | ❌淘汰 |
| List<T> | T 数组 | 强类型、索引、扩容 | 列表、批量数据 | ✅首选 |
| Queue<T> | 循环数组 | FIFO 先进先出 | 任务队列、消息 | ✅常用 |
| Stack<T> | 数组 | LIFO 后进先出 | 撤销、递归 | ✅常用 |
| Hashtable | 哈希表 | object 键值、装箱 | 老旧项目 | ❌淘汰 |
| Dictionary | 泛型哈希 | 强类型、极速查键 | 缓存、映射关系 | ✅首选 |
| SortedList | 有序数组 | 按键自动排序 | 需要有序键值 | ⭐按需使用 |
二、泛型:解决装箱拆箱与类型复用
2.1 装箱 & 拆箱底层原理
- 装箱:值类型 → object 引用类型(堆分配,性能损耗)
- 拆箱:object → 原始值类型(强制转换)
csharp
运行
// 装箱 int a = 10; object obj = a; // 拆箱 int b = (int)obj;非泛型容器存储值类型时会频繁装箱,大量循环下性能断崖下跌,泛型从根源解决该问题。
2.2 泛型基础:泛型方法 / 泛型类
泛型将类型参数延迟到调用时指定,编译器 JIT 会为每种 T 生成专属代码,无装箱。
泛型方法示例
csharp
运行
// T为类型占位符 public static void Show<T>(T data) { Console.WriteLine($"数据:{data},类型:{typeof(T)}"); } // 调用自动推导类型 Show(123); Show("测试文本"); Show(DateTime.Now);泛型类示例
csharp
运行
class MyContainer<T> { private T _data; public void Set(T val) => _data = val; public T Get() => _data; } // 使用 MyContainer<string> strBox = new MyContainer<string>(); strBox.Set("泛型测试");2.3 五大泛型约束 where
通过where限制 T 的类型范围,访问类型自有属性 / 方法:
where T : struct:必须是值类型where T : class:必须是引用类型where T : new():必须有无参公共构造函数(放最后)where T : 基类:必须继承该类where T : 接口:必须实现该接口
csharp
运行
// 多重约束,new()写末尾 public static void PrintUser<T>(T user) where T : People, ISay, new() { user.SayHi(); } class People { public int Id; } interface ISay { void SayHi(); }三、异常处理:try-catch-finally & 自定义异常
3.1 常见系统异常
IndexOutOfRangeException:数组 / 集合下标越界NullReferenceException:空对象调用成员DivideByZeroException:除零错误IOException:文件读写失败ArgumentException:参数非法
3.2 核心语法:try-catch-finally
- try:存放可能报错代码
- catch:捕获对应异常,精准异常写在前,Exception 兜底
- finally:无论是否报错,一定会执行,用于释放资源(文件、网络连接)
csharp
运行
static void ReadFileDemo() { FileStream fs = null; try { fs = new FileStream("test.txt", FileMode.Open); byte[] buf = new byte[1024]; fs.Read(buf,0,buf.Length); } catch(FileNotFoundException ex) { Console.WriteLine("文件不存在:"+ex.Message); } catch(Exception ex) { Console.WriteLine("未知错误:"+ex); } finally { // 释放流资源 fs?.Close(); fs?.Dispose(); } }3.3 using 语法糖(替代手动释放资源)
实现IDisposable接口的对象(文件流、数据库连接)可用using,编译自动生成try-finally释放资源,简化代码:
csharp
运行
// 等价上方finally释放逻辑 using(FileStream fs = new FileStream("test.txt",FileMode.Open)) { byte[] buf = new byte[1024]; fs.Read(buf,0,buf.Length); }3.4 throw 三种写法避坑
throw ex;❌ 不推荐:重置异常堆栈,丢失原始报错行throw;✅ 推荐:保留完整异常栈throw new Exception("提示",ex);✅ 包装异常,形成异常链(InnerException)
csharp
运行
try { int a = 1 / 0; } catch(DivideByZeroException ex) { // 包装内部异常,便于日志排查 throw new BusinessException("计算出错", ex); }3.5 自定义异常
业务场景区分系统异常,继承Exception:
csharp
运行
public class BusinessException : Exception { public BusinessException(){} public BusinessException(string msg):base(msg){} public BusinessException(string msg,Exception inner):base(msg,inner){} } // 抛出自定义异常 throw new BusinessException("用户余额不足");四、IO 文件与流操作(System.IO)
4.1 静态工具类:Path / File / Directory
Path:路径处理(不操作物理文件)
csharp
运行
string path = @"C:\Desktop\demo.txt"; Console.WriteLine(Path.GetFileName(path)); // demo.txt Console.WriteLine(Path.GetExtension(path)); // .txt Console.WriteLine(Path.GetDirectoryName(path)); // C:\Desktop Console.WriteLine(Path.Combine(@"C:\Desktop","a.txt")); // 拼接路径File:文件静态读写(小文件首选)
csharp
运行
// 一次性读取全部文本 string text = File.ReadAllText("demo.txt",Encoding.UTF8); // 按行读取 string[] lines = File.ReadAllLines("demo.txt"); // 覆盖写入 File.WriteAllText("demo.txt","写入内容"); // 追加写入 File.AppendAllText("demo.txt","追加文字"); // 二进制读写(图片、视频) byte[] data = File.ReadAllBytes("1.jpg"); File.WriteAllBytes("copy.jpg",data);Directory:文件夹操作
csharp
运行
Directory.CreateDirectory(@"C:\test"); // 创建文件夹 Directory.Delete(@"C:\test",true); // true递归删除所有子文件 bool hasFolder = Directory.Exists(@"C:\test");4.2 流分类(Stream 抽象基类)
FileStream:字节流,读写任何文件(图片 / 视频 / 文本)StreamReader/StreamWriter:字符流,专门处理文本,自带编码BufferedStream:缓冲流,提升大文件读写性能MemoryStream:内存流,无磁盘 IO
文本读写示例(StreamReader/StreamWriter)
csharp
运行
// 读取 using(StreamReader sr = new StreamReader("demo.txt",Encoding.UTF8)) { while(!sr.EndOfStream) { string line = sr.ReadLine(); } } // 写入,第二个参数true=追加 using(StreamWriter sw = new StreamWriter("demo.txt",true,Encoding.UTF8)) { sw.WriteLine("新增一行"); }五、进程与多线程同步(System.Threading)
5.1 Process 进程类:启动程序 / 查看进程
csharp
运行
// 打开记事本 Process.Start("notepad"); // 遍历所有进程,关闭Edge foreach(var p in Process.GetProcesses()) { if(p.ProcessName.ToLower() == "msedge") { p.Kill(); } }5.2 Thread 基础线程
主线程:Main方法线程;子线程用于耗时操作(文件、网络、计算)
无参 / 有参线程创建
csharp
运行
// 无参线程 Thread t1 = new Thread(DoWork); t1.Start(); // 带object参数线程 Thread t2 = new Thread(DoParamWork); t2.Start("传入参数"); static void DoWork() { Console.WriteLine("子线程执行"); } static void DoParamWork(object arg) { Console.WriteLine(arg.ToString()); }常用线程方法
Start():启动线程Sleep(毫秒):当前线程休眠Join():阻塞主线程,等待子线程执行完毕IsBackground=true:后台线程,程序退出自动销毁
5.3 线程同步锁 lock(解决资源竞争)
多线程同时修改同一变量会出现数据错乱,用 lock 锁定临界资源:
csharp
运行
class AppleDemo { static int appleCount = 10; // 私有静态锁对象,禁止string、this、typeof static readonly object locker = new object(); static void Main() { Thread t1 = new Thread(EatApple); Thread t2 = new Thread(EatApple); t1.Start("张三"); t2.Start("李四"); } static void EatApple(string name) { while(true) { lock(locker) { if(appleCount <= 0) break; appleCount--; Console.WriteLine($"{name}吃苹果,剩余{appleCount}"); Thread.Sleep(500); } } } }lock 避坑:不要锁字符串、不要锁实例 this,推荐私有静态只读 object。
5.4 Mutex 跨进程互斥锁
用于限制程序单开,多个 exe 互斥:
csharp
运行
bool newMutex; Mutex mutex = new Mutex(true,"SingleApp",out newMutex); if(!newMutex) { Console.WriteLine("程序已启动,直接退出"); return; }六、全套知识点总结
- 集合选型:新项目全部使用泛型
List<T>、Dictionary<TKey,TValue>,摒弃 ArrayList、Hashtable;栈队列按需选用Stack<T>/Queue<T>。 - 泛型核心:消除装箱拆箱,编译类型安全,
where约束拓展泛型能力。 - 异常规范:分层捕获异常,优先使用
throw保留堆栈,资源必须用using释放。 - IO 操作:小文件用 File 静态方法,大文件使用带缓冲流;文本用 StreamReader,多媒体用 FileStream 字节流。
- 多线程规范:共享资源必须加 lock 同步,区分前后台线程,避免死锁。
七、面试高频问答
- ArrayList 和 List<T>区别? 答:ArrayList 存储 object,装箱拆箱、无类型校验;List<T>泛型强类型,无性能损耗,编译报错提前拦截。
- Dictionary 查找速度为什么快? 答:底层哈希表,通过哈希码定位,平均 O (1) 查询效率。
- using 的底层原理? 答:实现 IDisposable 接口,编译生成 try-finally 块,自动调用 Dispose 释放非托管资源。
- 多线程不加锁会出现什么问题? 答:共享变量读写错乱、数据脏读,业务逻辑结果不符合预期。
