
简介面向C#初学者与进阶开发者这份《C#程序100实例》以100个精心设计的代码示例串联核心知识点覆盖从变量声明、数据类型、控制结构等基本语法到类与对象、封装继承多态、异常处理、事件与委托再到LINQ查询、文件I/O和async/await异步编程等进阶主题帮助读者通过动手实践从理论平滑过渡到实际项目。压缩包共包含1370个文件体积仅3.49MB其中以200个cs源码文件为核心配有sln/csproj工程文件、exe可执行程序、pdb调试符号以及大量bmp/resx/resources等界面资源便于直接打开工程运行和对照学习。包内另附“中国IT认证实验室学习下载频道.txt”可获取延伸学习链接。示例由浅入深每个实例对应明确的语法点或应用场景方便按需查阅和强化训练。目前已有126人学习下载适合作为C#入门强化与日常查阅的实用资料。 不少人问过我同一个问题C#这语言到底怎么学才快我每次给的答案都差不多——找一本《C#程序100实例》或者类似的实例集别光看直接上手敲。语法书读十遍不如亲手写完一个例子记得牢。这套100实例的合集算是我入行以来一直推荐给新人的入门路径很多公司带实习生也是这个思路拿一套覆盖字符串、集合、多线程、文件操作、窗体界面的实例挨个过。今天这篇就以前段时间我重新刷一遍这套100实例的体验为主线把里面的经典案例、容易踩的坑和背后的知识点串一遍希望对正在学C#或者准备往WinForm、上位机、管理系统这类方向深入的朋友有点实际帮助。1. 实例集的价值为什么从100个实例入手1.1 实例驱动学习的底层逻辑很多初学者有个共同的困惑语法书看了一整本关键字都认识但合上书之后什么都写不出来遇到一个真实需求直接懵。这个问题的根源在于语言知识是“点”真实项目是“线”和“面”中间缺了一座由大量微型项目搭起来的桥。实例集正好补上这块。每一个实例本质上都是一个微缩版项目哪怕是几行代码的字符串截取也包含了“我要处理什么数据、用哪个API、输出什么结果”这个完整链路。100个实例重复100次这种链路代码直觉就慢慢建立起来了。我见过不少新人前20个实例写得磕磕绊绊到第50个左右就开始顺手第80个以后基本能自己推测出某个功能大概用什么类、什么方法。这就是量变到质变的过程。还有一个额外收益实例集通常会覆盖很多平时根本不会主动去碰的冷门API。比如压缩包内文件枚举、系统网络信息读取这些日常工作里不一定会用但真遇到的时候你会想起“好像在哪个实例里见过”顺着记忆去查效率高很多。1.2 这100个实例如何分类更高效我的建议是别按书里的顺序从头到尾硬刷先把100个实例按功能域分个类再结合自己的工作方向挑重点突破。下面是这套实例集中比较常见的分类方式我在刷的时候就是按这个思路拆的。分类覆盖实例举例学习目标语言基础变量与类型、循环、方法重载、枚举、异常处理扎实语法、能独立阅读报错信息字符串与集合字符串截取、拼接、正则匹配、List/Dictionary操作掌握高频数据操作文件与IO读写文本、枚举目录、压缩包处理学会处理磁盘数据多线程与异步Thread、Task、CancellationToken、锁掌握并发控制WinForm界面窗体布局、控件绑定、DataGridView能搭建桌面管理界面数据库访问ADO.NET、参数化查询、ORM基础完成增删改查网络与串口SerialPort、TcpClient、HttpClient数据采集、上位机通信互操作与系统P/Invoke调用DLL、注册表、系统信息读取与外部组件和系统对接分类完之后优先刷和当前工作或学习目标相关的类别。比如目标是上位机开发就先把串口、线程、WinForm这几类实例吃透目标是做管理系统数据库和DataGridView部分就得滚瓜烂熟。剩下的类别可以留到后面当查漏补缺的素材不用一次全刷完。2. 实例拆解基础类实例的实践要点2.1 字符串处理实例截取字符串的多种姿势字符串处理是实例集里看起来最简单、但实际坑最多的一类。热搜词里“C#语言怎样截取字符串”出现频率很高说明这个问题确实是许多人的日常痛点。以最典型的场景为例给一个文件路径要提取出文件名和不带扩展名的部分。string path C:\Users\admin\Documents\report_2024.pdf; // 方法一Substring LastIndexOf string fileName path.Substring(path.LastIndexOf(\\) 1); string nameOnly fileName.Substring(0, fileName.LastIndexOf(.)); Console.WriteLine(nameOnly); // 输出report_2024 // 方法二Split var parts path.Split(\\, .); // parts [C:, Users, admin, Documents, report_2024, pdf]这两种方式各有适用场景。Substring加LastIndexOf的思路更贴近“我只要某个位置区间”的语义内存分配少性能更好适合大字符串、高并发场景。Split的优势是可读性强把路径分段后还能继续用索引访问其他部分但如果只是提取一个字段会产生一个完整数组的额外分配在循环里频繁调用时有性能损耗。实例集里还有一个值得重视的点字符串拼接。很多人刚学的时候喜欢直接写str item循环一多性能就崩。var sb new StringBuilder(); for (int i 0; i 10000; i) { sb.Append(i).Append(,); }原因是string在C#里是不可变类型每次都会在托管堆上创建一个新字符串对象原来的对象变成垃圾等待回收。10000次循环就是10000次分配而StringBuilder内部维护一个可变缓冲区只在容量不够时才扩容。这个知识点同时是面试高频题“string和StringBuilder的区别”的答案来源实例刷到这里的时候一定要停下来想清楚原理别只顾着抄代码。2.2 参数传递与线程管理实例引用类型与并发控制基础类实例里参数传递是另一个必考且必用的话题。热搜词里有“c# 引用类型参数”说明很多人对值类型、引用类型作为参数时的行为差异犯迷糊。简单说值类型默认按值传递方法内部修改不会影响外部变量引用类型传的是对象引用的副本方法内部修改对象成员会影响外部。而ref关键字让值类型也按引用传递直接操作原始变量本身。void Swap(ref int a, ref int b) { (a, b) (b, a); } int x 3, y 5; Swap(ref x, ref y); // 此时x5, y3out关键字和ref类似但它的语义是“这个方法负责输出一个值”所以必须在方法内部赋值而且调用时不需要提前初始化。in关键字则是只读引用传递用于避免大结构体拷贝同时保护参数不被修改。这三个关键字的工作原理实例集里通常会各配一个小例子我建议把这几个例子对照着跑一遍比单纯背定义有效得多。线程管理实例是这套合集里含金量比较高的部分。热搜词里有“c#查询线程并中止线程”这确实是一个经典场景。不过要提醒一句现在写新代码基本不推荐用Thread.Abort方法来中止线程因为Abort会直接向目标线程抛ThreadAbortException线程内正在执行的finally、资源释放逻辑可能被打断容易留下不稳定状态。正确做法是用协作式取消也就是CancellationToken。var cts new CancellationTokenSource(); var task Task.Run(() { while (true) { cts.Token.ThrowIfCancellationRequested(); // 这里写实际的业务逻辑 Thread.Sleep(200); } }, cts.Token); // 某个时刻需要停止时 cts.Cancel();这个模式的精髓在于“协作”两个字外部只是发一个取消信号线程自己在安全的位置检查信号并退出。你可以在每次循环开头检查也可以放在耗时操作前后完全由你控制退出时机资源释放也能按预期执行。实例集里如果还在教Thread.Abort建议直接跳过按上面这个模式来写。2.3 查漏补缺实例背后藏着的基础知识清单刷完基础类实例后我建议做一个自我检验不看资料能不能清晰解释以下问题。值类型和引用类型在内存上有什么区别struct和class的典型使用场景分别是什么为什么说string是不可变的字符串拼接时到底发生了什么线程和Task是什么关系什么时候应该用线程池委托和事件之间是什么关系事件为什么只能由声明类内部触发异常捕获中finally的作用是什么什么时候finally里的代码不会执行这些问题普遍出现在C#面试题中也直接对应实例集里的具体案例。能用自己的话讲清楚的说明基础类实例是真正吸收了讲不清的回头找到对应的实例断点调试一遍再看效果比硬背书好很多。3. 工程实战类实例WinForm、安装包与上位机3.1 WinForm窗体与控件从布局到数据绑定这套100实例里WinForm相关的实例历来是重头戏因为国内大量管理系统、工业软件仍然跑在WinForm上。信息管理系统是出现频率最高的实例类型核心操作就是一个DataGridView绑定数据源加上几个增删改查按钮。var dt new DataTable(); // 这里填充数据比如从数据库读取 dataGridView1.DataSource dt;这里有几个容易被忽略的细节。第一DataSource可以接DataTable、List 、BindingSource等不同类型它们在显示和更新机制上有差异。DataTable在不需要跟踪单个对象状态时最方便List 配合BindingList 使用时能自动反映集合增删但普通List不会自动刷新界面。第二DataGridView的AutoGenerateColumns默认是true它会根据数据源自动生成列这在快速原型里很省事但到了正式项目里最好手动配置列把列名、宽度、格式都控制住避免用户看到一堆没意义的英文字段名。跨线程访问控件是另一个高频问题。SerialPort的DataReceived事件、后台线程的回调里直接修改UI控件会抛“线程间操作无效”的异常。新手常见的错误做法是在窗体构造函数里把CheckForIllegalCrossThreadCalls设为false这等于把安全检查关掉数据竞争问题会被掩盖而不是被解决。正确姿势是使用Invoke把操作封送到UI线程。this.Invoke(new Action(() { label1.Text data; }));3.2 安装包制作从零到可分发开发完WinForm程序接下来必然是分发部署问题。热搜词里“c#的winform如何制作安装包”说明这是所有桌面端开发者绕不过去的一关。目前主流方案有两种我分开说。第一种是Visual Studio Installer Projects扩展这是老牌做法。安装这个扩展后在解决方案上右键添加新项目选Setup Project。然后在File System编辑器里配置Application Folder把主项目的Primary Output加进去再在Users Desktop和Users Programs Menu里创建快捷方式。生成之后会得到一个Setup.exe和若干.msi文件用户双击即可安装还能通过控制面板卸载。这套流程我建议至少完整做一遍能够理解安装包的基本结构。第二种是.NET 6以上版本开始推荐的单文件发布特别适合不需要注册表、不需要开始菜单快捷方式的绿色软件场景。dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFiletrue这样发布出来的就是一个独立的exe文件目标机器不需要预装.NET运行时拷贝过去就能跑。我实测下来最常见的坑有两个一个是杀毒软件误报自包含单文件容易触发启发式查杀需要给exe做数字签名或者额外加白名单说明另一个是文件体积大一个Hello World级别的程序发布出来可能就有60MB以上这是把整个运行时都打进去了属于正常现象介意体积的话可以用ReadyToRun或裁剪发布来权衡。无论是哪种方式安装包做完后一定要拿到一台干净的虚拟机或新电脑上实际安装验证。开发机能跑不代表目标机能跑缺VC运行库、缺.NET桌面运行时、目标系统版本过老这些问题只会在干净环境里暴露。3.3 上位机开发框架串口通信与数据采集上位机开发是C#生态里非常吃香的方向也是实例集里最值得深挖的实战类型。核心思路不复杂通过串口、网口或USB与下位机单片机、PLC、传感器等通信读取数据并展示在界面上同时下发控制指令。串口通信的入门实例基本是这个模板using System.IO.Ports; var sp new SerialPort(COM3, 9600, Parity.None, 8, StopBits.One); sp.DataReceived (s, e) { var data sp.ReadExisting(); // 注意这是后台线程必须Invoke才能更新UI this.Invoke(new Action(() txtLog.AppendText(data))); }; sp.Open();这段代码看着简单实际项目里要处理的问题多得多。第一个是分包和粘包串口数据是流式的下位机可能一次发来半个帧也可能连续几个帧粘在一起。常用的解决方案是定义通信协议比如帧头帧尾加长度字段收到数据后先丢进一个字节缓存再按协议从缓存里解析出完整的帧。第二个是串口打开失败的处理比如设备被拔掉、端口被其他程序占用要在代码里对UnauthorizedAccessException、IOException做专门处理并且加上重试机制。第三个是界面卡顿接收数据的同时不能阻塞UI线程这又回到了前面说的线程调度问题。调试上位机实例时没有实体硬件怎么办可以用虚拟串口工具创建一对互相连接的COM端口用另一个串口调试助手模拟下位机发数据。整个过程和真实硬件调试基本一致非常适合在家练手。实测下来把波特率设成和实际设备一致数据格式严格按协议解析绝大多数通信问题都能通过这种虚拟环境复现和排查。4. 进阶互操作实例Dll调用与数据库适配4.1 C#调用C/C DLL从P/Invoke到异常排查互操作类实例里C#调用C/C的DLL是最有含金量的内容之一。热搜词里有“c#dll调用c\c dll 报错system.accessviolationexception”这个报错我当年踩过确实非常有代表性。先看一个最简单的场景C那边导出一个函数extern C __declspec(dllexport) int Add(int a, int b) { return a b; } extern C __declspec(dllexport) void CopyData(char* buffer, int size) { for (int i 0; i size; i) buffer[i] (char)i; }C#这边对应的P/Invoke声明是[DllImport(MyLib.dll, CallingConvention CallingConvention.Cdecl)] public static extern int Add(int a, int b); [DllImport(MyLib.dll, CallingConvention CallingConvention.Cdecl)] public static extern void CopyData(IntPtr buffer, int size);AccessViolationException的常见原因基本就集中在几个地方。第一是函数签名不对称参数个数、参数类型、返回值类型和DLL里实际不一致调用时堆栈被破坏。第二是调用约定不一致C默认的cdecl和C#侧默认的StdCall也就是Winapi不匹配参数入栈和清理方式不同直接崩。这一点最隐蔽因为编译器不会报错只有运行到调用那一刻才炸。第三是缓冲区问题传出去的byte[]或IntPtr没有分配足够空间C侧往里写就越界了。排查时我建议按这个顺序来先用dumpbin工具查看DLL的导出函数名确认真实签名再对照C#声明检查参数类型和调用约定最后用Marshal.AllocHGlobal分配一块够大的缓冲区配合Marshal.Copy读写避免托管数组和指针的边界问题。int size 256; IntPtr buffer Marshal.AllocHGlobal(size); try { CopyData(buffer, size); byte[] result new byte[size]; Marshal.Copy(buffer, result, 0, size); } finally { Marshal.FreeHGlobal(buffer); }这套排查思路不只在DLL调用上有用很多涉及到和底层组件交互的问题本质上都是“边界处的约定不一致”慢慢排查总能找到原因。4.2 国内数据库适配实例达梦数据库连接实例集里的数据库章节传统教材一般只讲SQL Server和MySQL但实际项目里会遇到各种其他数据库。热搜词里的“c#支持达梦”就指向了这类需求。达梦数据库在国内政企项目中比较常见C#连达梦的方式和SQL Server有相似之处但需要专用的托管驱动DmProvider。使用时先在项目里引用DmProvider.dll然后连接字符串类似下面这样string connStr Server127.0.0.1;Port5236;DatabaseDAMENG;User IdSYSDBA;PasswordSYSDBA;; using var conn new DmConnection(connStr); conn.Open();写SQL的时候要注意达梦支持Oracle和SQL Server两种语法风格分页查询的写法要区分场景。有的项目里用ROWNUM有的用LIMIT具体取决于达梦配置的兼容模式。这个适配点是最容易踩坑的从SQL Server迁到达梦时开发库上跑通的分页SQL到正式库可能就报错排查方向基本都在兼容模式上。这类国产数据库适配的实际意义在于如果只学过SQL Server一种数据库换一个环境就懵而实例集如果能覆盖多种数据库的访问差异遇到新数据库时就有能力参考文档快速上手因为核心思路是一样的——驱动DLL加连接字符串加SQL语法差异点处理。4.3 文件与系统信息操作实例文件操作类实例里压缩包处理是实用频率很高的一个。热搜词“c#获取压缩包里的文件数量”就来自这个场景。用System.IO.Compression可以很简洁地实现using System.IO.Compression; using var zip ZipFile.OpenRead(C:\temp\archive.zip); int fileCount zip.Entries.Count(e !e.FullName.EndsWith(/)); Console.WriteLine($文件数量{fileCount});注意这里过滤了以斜杠结尾的目录项因为压缩包里既有文件也有目录条目直接数Entries会把目录也算进去。系统信息类实例有一个很实用但容易被忽视的点获取当前连接的WiFi名称。可以用netsh命令加输出解析来做var psi new ProcessStartInfo(netsh, wlan show interfaces) { RedirectStandardOutput true, UseShellExecute false, CreateNoWindow true }; using var proc Process.Start(psi); string output proc.StandardOutput.ReadToEnd(); var match Regex.Match(output, SSID\s*:\s*(.));不过这套方案有个问题中文和英文系统上netsh输出的字段名不一样中文系统输出可能是“SSID”英文系统也是“SSID”但周边字段不同解析正则写得太死容易翻车。更稳妥的做法是调用WMI或者Native Wifi API虽然代码量更大但跨语言版本表现更稳定。这个实例能给到最重要的经验是能用API就别解析命令行输出命令行的本地化问题是很大的变数。5. 实例学习的正确姿势与常见问题速查5.1 三个让实例“活”起来的方法刷过一遍实例不代表能应用到项目里把实例从“抄代码”变成“自己的代码”我验证过三个有效方法。第一个方法是改需求。原实例是控制台程序输出结果就改成WinForm界面展示原实例是硬编码数据就改成允许用户输入。改动过程中会自然遇到输入校验、类型转换、异常处理这些原实例没考虑的问题而解决这些问题的过程才是真正长本事的过程。第二个方法是写注释。不是抄原注释而是用自己的话把每一段代码的意图写出来重点记录“为什么这么写”而不是“这是什么”。比如写上“这里用StringBuilder而不是string 因为循环里拼接会产生大量中间字符串”这条注释的价值远大于背诵任何API文档。第三个方法是讲给别人听。找一个同样在学C#的朋友把自己的实现思路从头到尾讲一遍讲不清的地方就是自己的知识盲区回到代码里重新研究。这个方法看起来很笨但效率极高。5.2 常见报错排查速查表把我在刷实例和实际项目中遇到的典型报错整理成一个速查表遇到问题时可以照着排查。报错现场常见原因排查方向System.AccessViolationExceptionDLL签名或调用约定不匹配用dumpbin确认导出函数检查参数类型和CallingConvention线程间操作无效后台线程直接操作UI控件用Invoke封送到UI线程安装包在目标机无法运行目标机缺少.NET运行时或VC运行库安装Prerequisites或改用自包含发布串口数据乱码或丢失波特率不匹配、分包粘包未处理核对波特率和数据格式在接收缓存上做协议解析无法连接数据库连接字符串端口错误、驱动未安装测试端口连通性检查驱动DLL是否已引用找不到指定的DLL依赖的DLL不在运行目录把DLL放到exe同目录或设置DllImport的路径5.3 面试题与实例的对照关系C#面试题和这套100实例的重合度相当高。搜索词里就有“c#面试题”往届应届生刷面试题刷到崩溃的问题其实大部分在实例集里都有对应场景。比如ref与out的区别对应参数传递实例值类型与引用类型的区别对应变量定义实例string与StringBuilder的区别对应字符串拼接实例Thread与Task的区别对应线程管理实例装箱和拆箱对应集合存储值类型实例委托和事件对应事件驱动实例。我的建议是不要直接背面试题答案而是先把对应实例代码写一遍理解代码为什么这么做再回头去看面试题。有了上下文那些看似抽象的概念一下子就立体了。比如理解了装箱是什么才能理解为什么List 比ArrayList性能好那么多——因为ArrayList里存的是object每个int都要装箱成对象而List 直接存原始值不存在装箱开销。这个知识点在刷完集合类实例后很容易体会出来比单纯背结论牢固得多。我刷完这100个实例最大的收获不是记住了多少API而是养成了“先写再查”的习惯。遇到不会的功能先凭直觉写出第一版再查文档或找更好的写法。实例集里的代码很多是简化版放到真实项目里还要考虑资源释放、异常处理、返回值校验这些是实例没教但必须自己补的课。最后给个小建议不要把这100个实例当任务清单按顺序扫描挑十几个和你的工作方向相关的认真吃透比全刷一遍有效得多。本文还有配套的精品资源点击获取