C# 从集合、泛型、异常到 IO、多线程全套入门笔记

C# 从集合、泛型、异常到 IO、多线程全套入门笔记

摘要:本文整合 C# 基础进阶五大核心模块:非泛型 / 泛型集合、泛型原理与约束、异常处理机制、文件 IO 流操作、进程与多线程同步,配套完整可运行代码,区分新旧容器优劣,总结开发避坑点,适合零基础入门、面试突击复习。 标签:C#; .NET; 集合;泛型;多线程;IO 流;异常处理 分类:后端开发 / C# 桌面开发

前言

初学 C# 时很容易混淆ArrayListList、分不清栈 / 队列使用场景、写文件忘记释放资源、多线程并发出现数据错乱。本文结合课堂全套笔记,把集合容器、泛型、异常、IO 文件、多线程五大高频知识点一次性梳理清楚,所有案例均可直接复制运行,同时标注生产环境推荐写法与淘汰 API。

一、集合容器:线性容器 & 键值容器

1.1 非泛型容器(System.Collections,新项目不推荐)

1.1.1 ArrayList 动态数组

本质是object[],可存任意类型,存在装箱拆箱损耗,无类型校验。

核心特性
  1. 容量自动扩容,默认初始容量 16;
  2. 支持Add()尾部添加、Insert(index,obj)指定位置插入;
  3. 删除:Clear()清空、Remove(元素)删首个匹配、RemoveAt(索引)RemoveRange(起始,数量)
  4. 查询:Contains()判断存在、IndexOf()获取索引;
  5. 属性: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,队列仅剩消息2
1.1.3 Stack 栈(LIFO 后进先出)

场景:撤销操作、递归回溯、表达式求值。

  • 入栈:Push(obj)
  • 出栈:Pop()
  • 查看栈顶:Peek()

csharp

运行

Stack stack = new Stack(); stack.Push("页面A"); stack.Push("页面B"); Console.WriteLine(stack.Pop()); // 页面B
1.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:C

1.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 容器选型对照表

表格

容器存储结构特性适用场景推荐度
ArrayListobject 数组任意类型、装箱老旧维护项目❌淘汰
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 的类型范围,访问类型自有属性 / 方法:

  1. where T : struct:必须是值类型
  2. where T : class:必须是引用类型
  3. where T : new():必须有无参公共构造函数(放最后)
  4. where T : 基类:必须继承该类
  5. 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 常见系统异常

  1. IndexOutOfRangeException:数组 / 集合下标越界
  2. NullReferenceException:空对象调用成员
  3. DivideByZeroException:除零错误
  4. IOException:文件读写失败
  5. 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 三种写法避坑

  1. throw ex;❌ 不推荐:重置异常堆栈,丢失原始报错行
  2. throw;✅ 推荐:保留完整异常栈
  3. 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 抽象基类)

  1. FileStream:字节流,读写任何文件(图片 / 视频 / 文本)
  2. StreamReader/StreamWriter:字符流,专门处理文本,自带编码
  3. BufferedStream:缓冲流,提升大文件读写性能
  4. 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; }

六、全套知识点总结

  1. 集合选型:新项目全部使用泛型List<T>、Dictionary<TKey,TValue>,摒弃 ArrayList、Hashtable;栈队列按需选用Stack<T>/Queue<T>
  2. 泛型核心:消除装箱拆箱,编译类型安全,where约束拓展泛型能力。
  3. 异常规范:分层捕获异常,优先使用throw保留堆栈,资源必须用using释放。
  4. IO 操作:小文件用 File 静态方法,大文件使用带缓冲流;文本用 StreamReader,多媒体用 FileStream 字节流。
  5. 多线程规范:共享资源必须加 lock 同步,区分前后台线程,避免死锁。

七、面试高频问答

  1. ArrayList 和 List<T>区别? 答:ArrayList 存储 object,装箱拆箱、无类型校验;List<T>泛型强类型,无性能损耗,编译报错提前拦截。
  2. Dictionary 查找速度为什么快? 答:底层哈希表,通过哈希码定位,平均 O (1) 查询效率。
  3. using 的底层原理? 答:实现 IDisposable 接口,编译生成 try-finally 块,自动调用 Dispose 释放非托管资源。
  4. 多线程不加锁会出现什么问题? 答:共享变量读写错乱、数据脏读,业务逻辑结果不符合预期。