C# 核心知识详解:集合、泛型、字典、文件IO与多线程
一、C# 集合Collections1.1 集合概述集合是 C# 中用于存储和管理一组相关对象的数据结构。与数组相比集合具有动态扩容的能力可以根据需要自动调整大小使用更加灵活。C# 中的集合主要分为两大类非泛型集合位于System.Collections命名空间如ArrayList、Hashtable等存储object类型存在装箱拆箱开销。泛型集合位于System.Collections.Generic命名空间如ListT、DictionaryTKey, TValue等类型安全且性能更好推荐优先使用。1.2 常用泛型集合类型集合类型说明适用场景ListT动态数组可按索引访问需要频繁遍历、按索引访问的场景DictionaryTKey, TValue键值对集合基于哈希表需要快速查找、键值映射的场景HashSetT无序不重复元素集去重、集合运算交并补QueueT队列先进先出FIFO任务排队、消息处理StackT栈后进先出LIFO表达式求值、撤销操作LinkedListT双向链表频繁在中间插入/删除元素1.3 ListT 详解ListT是最常用的泛型集合相当于增强版的数组。它内部维护一个数组当元素数量超过容量时自动扩容通常扩容为原来的 2 倍。常用属性与方法Count获取元素个数Capacity获取或设置内部容量Add(T item)在末尾添加元素AddRange(IEnumerableT collection)批量添加Insert(int index, T item)在指定位置插入Remove(T item)移除指定元素RemoveAt(int index)移除指定索引处的元素Contains(T item)判断是否包含某元素Sort()排序Find(PredicateT match)查找第一个匹配项1.4 代码示例using System; using System.Collections.Generic; class Program { static void Main() { // 创建一个 int 类型的 List Listint numbers new Listint(); // 添加元素 numbers.Add(10); numbers.Add(20); numbers.Add(30); // 批量添加 numbers.AddRange(new int[] { 40, 50 }); // 插入元素 numbers.Insert(0, 5); // 在索引 0 处插入 5 Console.WriteLine($元素个数: {numbers.Count}); Console.WriteLine($容量: {numbers.Capacity}); // 遍历 foreach (int num in numbers) { Console.Write(num ); } // 输出: 5 10 20 30 40 50 // 查找 int result numbers.Find(x x 25); Console.WriteLine($\n第一个大于25的数: {result}); // 30 // 排序降序 numbers.Sort((a, b) b.CompareTo(a)); // 移除 numbers.Remove(30); numbers.RemoveAt(0); } }二、泛型Generics2.1 泛型概述泛型是 C# 2.0 引入的重要特性允许在定义类、接口、方法时使用占位符类型参数直到使用时才指定具体类型。泛型的核心思想是类型参数化实现代码复用与类型安全的统一。没有泛型之前我们只能使用object类型来实现通用集合但这会带来两个问题类型不安全可以往集合里放任何类型运行时才可能报错。性能损耗值类型需要装箱boxing和拆箱unboxing开销较大。2.2 泛型的优势类型安全编译时检查类型避免运行时类型转换错误。高性能无需装箱拆箱值类型操作效率大幅提升。代码复用一份代码可适用于多种类型减少重复编写。可读性好代码意图更清晰一看就知道集合里装的是什么类型。2.3 泛型类与泛型方法泛型类泛型类在类名后使用T声明类型参数T 可以在类的字段、属性、方法中使用。// 定义一个泛型栈 public class MyStackT { private T[] _items; private int _count; public MyStack(int capacity 4) { _items new T[capacity]; _count 0; } public void Push(T item) { if (_count _items.Length) { Array.Resize(ref _items, _items.Length * 2); } _items[_count] item; } public T Pop() { if (_count 0) throw new InvalidOperationException(栈为空); return _items[--_count]; } public T Peek() { if (_count 0) throw new InvalidOperationException(栈为空); return _items[_count - 1]; } public int Count _count; }泛型方法泛型方法可以在非泛型类中定义方法本身带类型参数。public static class Utils { // 交换两个变量的值 public static void SwapT(ref T a, ref T b) { T temp a; a b; b temp; } // 查找数组中的最大值 public static T MaxT(T[] array) where T : IComparableT { if (array null || array.Length 0) throw new ArgumentException(数组不能为空); T max array[0]; foreach (T item in array) { if (item.CompareTo(max) 0) max item; } return max; } } // 使用示例 int a 10, b 20; Utils.Swap(ref a, ref b); // a20, b10 int[] nums { 3, 1, 4, 1, 5, 9, 2, 6 }; int max Utils.Max(nums); // 92.4 类型参数约束where通过where子句可以对类型参数施加约束限制可传入的类型范围从而在泛型代码中调用该类型的特定方法。约束说明where T : structT 必须是值类型where T : classT 必须是引用类型where T : new()T 必须有公共无参构造函数where T : 基类名T 必须继承自指定基类where T : 接口名T 必须实现指定接口// T 必须是引用类型且有默认构造函数 public T CreateInstanceT() where T : class, new() { return new T(); }三、字典DictionaryTKey, TValue3.1 字典概述DictionaryTKey, TValue是基于哈希表实现的键值对集合提供了接近 O(1) 的查找速度。每个元素都是一个KeyValuePairTKey, TValue结构。核心特点键Key必须唯一不能重复键不能为null值可以查找速度快基于哈希表实现元素无序不保证插入顺序3.2 常用操作操作方法/属性说明添加Add(key, value)添加键值对键已存在则抛异常添加/更新dictionary[key] value键存在则更新不存在则添加获取dictionary[key]按键取值键不存在则抛异常安全获取TryGetValue(key, out value)键存在返回 true 并输出值判断存在ContainsKey(key)判断是否包含指定键删除Remove(key)删除指定键的元素元素个数Count获取键值对数量清空Clear()移除所有元素3.3 代码示例using System; using System.Collections.Generic; class Program { static void Main() { // 创建字典学生ID - 学生姓名 Dictionaryint, string students new Dictionaryint, string(); // 添加元素 students.Add(1001, 张三); students.Add(1002, 李四); students.Add(1003, 王五); // 使用索引器添加/更新 students[1004] 赵六; // 添加 students[1001] 张三丰; // 更新 // 安全获取值 if (students.TryGetValue(1002, out string? name)) { Console.WriteLine($学号1002: {name}); // 李四 } // 遍历所有键值对 foreach (KeyValuePairint, string kvp in students) { Console.WriteLine($学号: {kvp.Key}, 姓名: {kvp.Value}); } // 只遍历键 foreach (int key in students.Keys) { Console.WriteLine($键: {key}); } // 只遍历值 foreach (string value in students.Values) { Console.WriteLine($值: {value}); } // 删除 students.Remove(1003); // 判断是否包含键 bool hasKey students.ContainsKey(1003); // false } }3.4 字典实战统计字符出现次数using System; using System.Collections.Generic; class Program { static void Main() { // 要统计的字符串 string text hello world, hello csharp; // 创建字典键为字符值为出现次数 Dictionarychar, int charCount new Dictionarychar, int(); // 遍历字符串中的每个字符 foreach (char c in text) { // 如果字典中已包含该字符则次数加1 if (charCount.ContainsKey(c)) { charCount[c]; } // 否则将该字符添加到字典中初始次数为1 else { charCount[c] 1; } } // 输出每个字符出现的次数 Console.WriteLine(字符统计结果); foreach (var kvp in charCount) { Console.WriteLine(${kvp.Key}: {kvp.Value}次); } // 可选按字符排序输出 Console.WriteLine(\n按字符排序输出); foreach (var kvp in charCount.OrderBy(x x.Key)) { Console.WriteLine(${kvp.Key}: {kvp.Value}次); } } }四、文件操作File4.1 File 类与 FileInfo 类C# 提供了两种方式操作文件File类静态类提供一系列静态方法适合单次操作。每次调用都会进行安全检查适合少量操作。FileInfo类实例类需要先创建对象适合对同一文件进行多次操作效率更高。两者都位于System.IO命名空间。4.2 常用文件操作文件读写using System; using System.IO; using System.Text; class Program { static void Main() { string path C:\temp\test.txt; // 写入文件 // 写入全部内容覆盖 File.WriteAllText(path, Hello, C# File!, Encoding.UTF8); // 追加写入 File.AppendAllText(path, \n追加一行内容); // 写入多行 string[] lines { 第一行, 第二行, 第三行 }; File.WriteAllLines(path, lines, Encoding.UTF8); // 读取文件 // 读取全部内容 string content File.ReadAllText(path, Encoding.UTF8); Console.WriteLine(content); // 按行读取 string[] readLines File.ReadAllLines(path, Encoding.UTF8); foreach (string line in readLines) { Console.WriteLine(line); } } }文件与目录管理using System; using System.IO; class Program { static void Main() { // 文件操作示例 string path C:\temp\test.txt; // 判断文件是否存在 bool exists File.Exists(path); Console.WriteLine($文件是否存在: {exists}); // 复制文件overwrite: true 表示覆盖已存在的目标文件 File.Copy(path, C:\temp\test_copy.txt, overwrite: true); Console.WriteLine(文件复制完成); // 移动文件重命名或移动到其他目录 File.Move(path, C:\temp\newdir\test.txt); Console.WriteLine(文件移动完成); // 删除文件 File.Delete(C:\temp\newdir\test.txt); Console.WriteLine(文件删除完成); // 目录操作示例 string dirPath C:\temp\mydir; // 创建目录如果目录已存在不会报错 Directory.CreateDirectory(dirPath); Console.WriteLine(目录创建完成); // 判断目录是否存在 bool dirExists Directory.Exists(dirPath); Console.WriteLine($目录是否存在: {dirExists}); // 获取目录下所有文件 string[] files Directory.GetFiles(dirPath); Console.WriteLine($目录下文件数量: {files.Length}); // 获取目录下所有子目录 string[] dirs Directory.GetDirectories(dirPath); Console.WriteLine($子目录数量: {dirs.Length}); // 删除目录recursive: true 表示递归删除子目录和文件 Directory.Delete(dirPath, recursive: true); Console.WriteLine(目录删除完成); } }4.3 FileInfo 示例using System; using System.IO; class Program { static void Main() { // 创建 FileInfo 对象 FileInfo fi new FileInfo(C:\temp\test.txt); // 检查文件是否存在 if (fi.Exists) { // 输出文件信息 Console.WriteLine($文件名: {fi.Name}); Console.WriteLine($完整路径: {fi.FullName}); Console.WriteLine($文件大小: {fi.Length} 字节); Console.WriteLine($创建时间: {fi.CreationTime}); Console.WriteLine($最后修改: {fi.LastWriteTime}); Console.WriteLine($扩展名: {fi.Extension}); Console.WriteLine($所在目录: {fi.DirectoryName}); } else { Console.WriteLine(文件不存在); } // 复制文件如果存在 if (fi.Exists) { fi.CopyTo(C:\temp\backup.txt, overwrite: true); Console.WriteLine(文件复制完成); } // 获取文件信息后需要调用 Refresh() 重新刷新文件状态 fi.Refresh(); Console.WriteLine($刷新后文件大小: {fi.Length} 字节); } }五、IO 流Stream5.1 流的概念流Stream是 .NET 中用于处理字节数据的抽象概念可以理解为数据的管道。数据从一个地方流向另一个地方比如从文件流向内存、从网络流向程序等。所有流都继承自System.IO.Stream抽象类具有以下基本操作读取Read从流中读取字节数据写入Write向流中写入字节数据定位Seek在流中移动当前位置关闭Close/Dispose释放资源5.2 常用流类型流类型说明用途FileStream文件流读写文件的字节数据MemoryStream内存流在内存中操作字节数据BufferedStream缓冲流为其他流增加缓冲提升性能NetworkStream网络流网络数据传输StreamReader/StreamWriter文本读写器以文本方式读写流字符级BinaryReader/BinaryWriter二进制读写器以二进制方式读写基本类型5.3 FileStream 字节读写using System; using System.IO; using System.Text; class Program { static void Main() { string path C:\temp\binary.dat; // 写入字节 using (FileStream fs new FileStream(path, FileMode.Create, FileAccess.Write)) { string text Hello Stream!; byte[] buffer Encoding.UTF8.GetBytes(text); fs.Write(buffer, 0, buffer.Length); } // 读取字节 using (FileStream fs new FileStream(path, FileMode.Open, FileAccess.Read)) { byte[] buffer new byte[fs.Length]; int bytesRead fs.Read(buffer, 0, buffer.Length); string text Encoding.UTF8.GetString(buffer, 0, bytesRead); Console.WriteLine(text); // Hello Stream! } } }using 语句实现了IDisposable接口的对象应该用using包裹确保使用完后自动释放资源无需手动调用 Close()。5.4 StreamReader / StreamWriter 文本读写StreamReader和StreamWriter是高层封装直接以字符为单位读写文本自动处理编码转换使用更方便。string path C:\temp\poem.txt; // StreamWriter 写入文本 using (StreamWriter writer new StreamWriter(path, append: false, Encoding.UTF8)) { writer.WriteLine(床前明月光); writer.WriteLine(疑是地上霜。); writer.WriteLine(举头望明月); writer.WriteLine(低头思故乡。); } // StreamReader 读取文本 using (StreamReader reader new StreamReader(path, Encoding.UTF8)) { // 逐行读取 string? line; while ((line reader.ReadLine()) ! null) { Console.WriteLine(line); } // 或者一次性读取全部 // string allText reader.ReadToEnd(); }5.5 二进制读写string path C:\temp\data.bin; // 写入二进制数据 using (BinaryWriter writer new BinaryWriter(File.Open(path, FileMode.Create))) { writer.Write(42); // int writer.Write(3.14f); // float writer.Write(true); // bool writer.Write(C# 编程); // string } // 读取二进制数据 using (BinaryReader reader new BinaryReader(File.Open(path, FileMode.Open))) { int intVal reader.ReadInt32(); float floatVal reader.ReadSingle(); bool boolVal reader.ReadBoolean(); string strVal reader.ReadString(); Console.WriteLine(${intVal}, {floatVal}, {boolVal}, {strVal}); }5.6 MemoryStream 内存流// 创建内存流 using (MemoryStream ms new MemoryStream()) { // 写入数据 byte[] data Encoding.UTF8.GetBytes(Hello Memory!); ms.Write(data, 0, data.Length); // 将流位置重置到开头 ms.Position 0; // 读取数据 byte[] buffer new byte[ms.Length]; ms.Read(buffer, 0, buffer.Length); string text Encoding.UTF8.GetString(buffer); Console.WriteLine(text); // Hello Memory! // 转为 byte[] 数组 byte[] result ms.ToArray(); }六、多线程Multithreading6.1 多线程概述多线程是指在一个进程中同时运行多个线程每个线程可以独立执行不同的任务。在 C# 中多线程可以让程序同时处理多项工作提高程序的响应速度和资源利用率。进程与线程的区别进程操作系统分配资源的基本单位每个进程有独立的内存空间。线程CPU 调度的基本单位一个进程可以包含多个线程线程共享进程的内存资源。C# 中实现多线程的方式主要有Thread 类最基础的线程创建方式控制力强但较底层。ThreadPool 线程池管理线程的集合适合短任务减少线程创建开销。Task 任务基于线程池更高级的抽象推荐使用。async/await异步编程模型简化异步代码编写。6.2 Thread 类Thread类位于System.Threading命名空间可以创建和控制线程。using System; using System.Threading; class Program { static void Main() { // 创建线程 Thread t1 new Thread(DoWork); Thread t2 new Thread(() CountNumbers(线程B, 10)); // 启动线程 t1.Start(); t2.Start(); // 主线程也做点事 for (int i 0; i 5; i) { Console.WriteLine($主线程: {i}); Thread.Sleep(500); // 休眠 500ms } // 等待线程结束 t1.Join(); t2.Join(); Console.WriteLine(所有线程执行完毕); } static void DoWork() { for (int i 0; i 5; i) { Console.WriteLine($线程A: {i}); Thread.Sleep(300); } } static void CountNumbers(string name, int count) { for (int i 0; i count; i) { Console.WriteLine(${name}: {i}); Thread.Sleep(200); } } }Thread 常用属性与方法Start()启动线程Sleep(int milliseconds)静态方法让当前线程休眠Join()等待线程结束IsAlive线程是否还在运行Name线程名称Priority线程优先级IsBackground是否为后台线程后台线程随主线程结束而结束6.3 Task 任务Task是 .NET 4.0 引入的更高级的多线程抽象基于线程池比 Thread 更易用、更高效。推荐在新代码中使用 Task。using System; using System.Threading.Tasks; class Program { static async Task Main() { // 方式一创建并启动任务 Task task1 Task.Run(() { Console.WriteLine(任务1开始执行); Thread.Sleep(1000); Console.WriteLine(任务1执行完毕); }); // 方式二有返回值的任务 Taskint task2 Task.Run(() { int sum 0; for (int i 1; i 100; i) { sum i; } return sum; }); // 等待任务完成并获取结果 int result await task2; Console.WriteLine($1到100的和: {result}); // 5050 await task1; Console.WriteLine(所有任务完成); } }6.4 async / await 异步编程async/await是 C# 5.0 引入的异步编程关键字让异步代码看起来像同步代码一样直观极大简化了异步编程的复杂度。async修饰方法表示这是一个异步方法。await等待异步操作完成不会阻塞线程。using System; using System.IO; using System.Threading.Tasks; class Program { static async Task Main() { Console.WriteLine(开始下载文件...); // 异步读取文件不会阻塞UI线程 string content await ReadFileAsync(C:\temp\test.txt); Console.WriteLine($文件内容长度: {content.Length}); Console.WriteLine(下载完成); } // 异步方法命名约定以 Async 结尾 static async Taskstring ReadFileAsync(string path) { using (StreamReader reader new StreamReader(path)) { // await 等待异步读取完成 return await reader.ReadToEndAsync(); } } }6.5 线程安全与锁多个线程同时访问共享资源时可能导致数据不一致。这就是线程安全问题。使用lock关键字可以确保同一时刻只有一个线程进入临界区。using System; using System.Threading; using System.Threading.Tasks; class Program { private static int _count 0; private static readonly object _lockObj new object(); static async Task Main() { // 启动 10 个任务每个任务累加 1000 次 Task[] tasks new Task[10]; for (int i 0; i 10; i) { tasks[i] Task.Run(IncrementCount); } await Task.WhenAll(tasks); // 如果不加锁结果很可能小于 10000 Console.WriteLine($最终计数: {_count}); // 加锁后: 10000 } static void IncrementCount() { for (int i 0; i 1000; i) { // lock 确保同一时刻只有一个线程执行下面的代码 lock (_lockObj) { _count; } } } }注意lock 的对象应该是private static readonly的引用类型避免使用this或字符串等可能被外部访问的对象防止死锁。6.6 常用线程同步方式同步方式说明适用场景lock互斥锁最简单常用保护临界区代码Monitorlock 的底层实现支持超时需要更细粒度控制Mutex跨进程互斥进程间同步Semaphore信号量限制并发数量控制同时访问资源的线程数AutoResetEvent事件通知线程间信号通知Concurrent 集合线程安全集合多线程环境下的集合操作 总结本文系统介绍了 C# 开发中的六大核心知识点让我们快速回顾一下集合泛型集合ListT、DictionaryTKey, TValue等是日常开发最常用的数据结构类型安全且性能好应优先使用。泛型通过类型参数化实现代码复用与类型安全是集合、委托等众多特性的基础。where约束可以限制类型参数范围。字典DictionaryTKey, TValue基于哈希表查找速度接近 O(1)适合键值映射场景。键必须唯一。文件操作File类适合单次操作FileInfo适合多次操作。Directory类用于目录管理。IO 流流是字节数据的管道。FileStream是底层字节流StreamReader/Writer是高层文本流MemoryStream在内存中操作数据。多线程推荐使用Task和async/await进行异步编程。多线程访问共享资源时要注意线程安全使用lock等同步机制保护数据。学习建议理论结合实践每个知识点都动手写代码跑一跑。多线程部分建议重点掌握 Task 和 async/await这是现代 C# 异步编程的主流方式。如果本文对你有帮助欢迎点赞、收藏、关注有任何问题欢迎在评论区交流讨论。