☰
C#实例驱动学习:从字符串截取到上位机开发实战
2026/10/3 5:54:05 网站建设 项目流程

简介:面向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); // 此时x=5, y=3

out关键字和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加进去,再在User's Desktop和User's Programs Menu里创建快捷方式。生成之后会得到一个Setup.exe和若干.msi文件,用户双击即可安装,还能通过控制面板卸载。这套流程我建议至少完整做一遍,能够理解安装包的基本结构。

第二种是.NET 6以上版本开始推荐的单文件发布,特别适合不需要注册表、不需要开始菜单快捷方式的绿色软件场景。

dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true

这样发布出来的就是一个独立的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 = "Server=127.0.0.1;Port=5236;Database=DAMENG;User Id=SYSDBA;Password=SYSDBA;"; 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个实例当任务清单按顺序扫描,挑十几个和你的工作方向相关的,认真吃透,比全刷一遍有效得多。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询