简介:面向C#初学者的源码合集,精心汇编100个入门程序与198个经典WinForm实例,覆盖变量声明、数据类型、分支循环、函数使用,以及类与对象、继承、多态等核心语法,同时延伸到控件交互、事件处理、窗体布局、数据库连接等桌面开发必备技能。压缩包为ZIP格式,整体体积约47.92MB,源码文件可直接用Visual Studio打开,边运行边阅读,节省自行整理案例的时间;目前已有897人学习下载,适合正在自学C#、准备课程设计或希望系统掌握Windows桌面应用开发的读者。学习时可以先运行示例观察界面,再逐行理解逻辑,通过修改参数、补全功能来加深印象;WinForm部分重点演示按钮、文本框、列表框、菜单、对话框等控件的典型用法,并涉及异常处理与资源释放,能帮助读者建立面向对象和事件驱动的整体思路,为后续项目开发打下扎实基础。
1. 拿到「C# 入门实例源码 + 经典 WinForm 实例合集」的压缩包,多数人的第一反应是解压、打开、按 F5,看到窗体弹出来就以为自己会了。但它的真正价值不在那 298 个窗口,而在补上 C# 语法和 WinForm 控件运用之间的断层
入门实例解决「看不懂语法书」的问题——字符串截取、类与对象、LINQ 查询;经典 WinForm 实例解决「会语法但做不出界面」的问题——DataGridView 绑定、Timer 采集时的 UI 卡顿、串口上位机的代码组织。
两套资源叠在一起,正好覆盖从Console.WriteLine到完整桌面应用的跨度。下文按环境对齐、语法主线、WinForm 高频模式、排错、沉淀复用五步展开,每步都有可直接抄的代码和参数说明。
2. 实例源码拿到手先做环境对齐:Visual Studio 版本与目标框架整理
2.1 解压后的目录规划与单项目入口定位
这类合集通常是一个大 zip 套几十个独立项目,每个项目单独一个文件夹。常见做法是先按「01_入门语法」「02_WinForm 控件」「03_综合案例」这类维度浏览目录名,不要把所有文件平铺解压到桌面,否则后面找启动项目会非常痛苦。
需要提前确认的一点:Visual Studio 2015 到 2022 都能打开这些老项目,但用 VS2019 打开旧格式项目时会弹「进行一次性的安全升级」,确认即可。真正要手动处理的是目标框架版本。VS2019 创建的新项目如果用了新csproj格式,VS2015 打不开且没有自动降级路径,所以如果只有 VS2015,优先从合集里挑旧格式项目跑。
检查项目目标框架
用文本编辑器随便打开任意一个.csproj,定位到:
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>不同项目可能写着v4.0、v4.5、v4.6.2甚至net8.0。系统缺少对应 .NET Framework 版本时,编译会报「无法解析依赖项」或「此项目需要 .NET Framework X.X」。用 PowerShell 快速确认本机装了哪些版本:
Get-ChildItem "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" | Get-ItemProperty | Select-Object ReleaseRelease 值对照关系如下:
| 项目里的 TargetFrameworkVersion | 注册表 Release 值至少为 | 说明 |
|---|---|---|
| v4.0 | 不适用(需单独装开发包) | 老项目最常见 |
| v4.5 | 378389 | 早期 Win8 环境 |
| v4.6.2 | 394802 | Win10 早期自带 |
| v4.7.2 | 461808 | 兼容性最好的中间版本 |
| v4.8 | 528040 | Win10/11 广泛可用 |
假如本机 Release 是 461808(4.7.2),而项目目标是 v4.5,其实可以直接打开运行,因为 .NET Framework 向后兼容,高版本运行时能跑低版本目标框架的程序。真正的坑是反过来的:目标 v4.7.2 而本机只有 4.5,这时要么装 4.7.2 Developer Pack,要么按第 5 章的批量替换降级目标框架。
2.2 批量加载解决方案与首次编译的预检查
如果合集里带.sln文件,直接打开即可看到全部项目。只有零散.csproj时,用「新建项目 → 其他项目类型 → Visual Studio 解决方案 → 空白解决方案」,然后右键解决方案 →「添加 → 现有项目」把项目逐个加进来。注意这里加的是csproj项目本身,不是「项目引用」,两者动作不同。
加载完成后的第一个动作不是按 F5,而是「生成 → 重新生成解决方案」,让编译错误一次性暴露。最常见的两类问题:项目缺System.Windows.Forms程序集引用,或者多个项目目标框架不一致。前者在引用管理器里勾选即可,后者统一留到第 5 章处理。
提示:如果某个项目使用了第三方界面美化组件,工具箱里对应控件会变成灰色,编译通过但运行时在
InitializeComponent抛「找不到程序集」。此时检查是否缺少 DLL 注册或 NuGet 还原。
2.3 首次运行前的三个自检
按 F5 之前快速过三件事:确认启动项目是你要看的那个窗体项目(右键解决方案 → 设置启动项目);确认Program.cs里Application.Run(new Form1())指向的窗体类存在;确认系统编码和代码里的中文字符不会在旧版编译器下出现乱码警告。
3. 100 个入门实例里的 C# 核心语法主线:字符串、面向对象与 LINQ
3.1 字符串截取与格式化:实例里出现频率最高的操作
100 个入门实例里几乎每个都碰过字符串,因为控制台和 MessageBox 的输入输出都绕不开它。最高频的三个操作是截取、拆分和格式化。
截取不要只会IndexOf加Substring的裸写法,IndexOf找不到目标时返回 -1,直接传给Substring会抛ArgumentOutOfRangeException。稳妥写法是先判断再截取:
string raw = "SN=ABC123,VER=1.0"; int pos = raw.IndexOf("SN="); if (pos >= 0) { string sn = raw.Substring(pos + 3, 6); Console.WriteLine(sn); // 输出 ABC123 }Substring的两个参数分别是起始索引和长度,不是起始和结束位置,这是初学者最容易记反的地方。按分隔符拆多段用Split:
string line = "192.168.1.10|502|S7-1200"; string[] parts = line.Split('|'); string ip = parts[0]; // 192.168.1.10 int port = int.Parse(parts[1]); // 502Split的'|'是字符重载,传字符串"|"也可以,但传空字符串会按字符逐个拆,这是隐藏陷阱。格式化输出方面,插值字符串$"{ip}:{port}"是现在的首选写法,老例子里常见的string.Format("{0}:{1}", ip, port)仍然有效,两者的区别只在于编译期检查和可读性。
3.2 类与对象:从过程式到事件驱动是入门的分水岭
实例从「一段static void Main写完所有逻辑」过渡到「定义一个类再组织行为」,是 C# 入门最陡的坡。建议按固定顺序读代码:先找字段看状态,再看构造方法看初始化,最后找公开方法看行为。比如一个Counter类,字段_count是状态,构造方法给初始值,AddOne()是行为。
事件是 WinForm 绕不开的面向对象概念,按钮点击、Timer 的 Tick、串口数据到达都是事件。初学者可以先不深究委托底层,把订阅语法焊死:
public class Counter { public event EventHandler? ValueChanged; private int _count; public void AddOne() { _count++; ValueChanged?.Invoke(this, EventArgs.Empty); } } // 使用方 var counter = new Counter(); counter.ValueChanged += (s, e) => Console.WriteLine($"计数变化,当前值可用 {s}"); counter.AddOne();event关键字定义事件,+=是订阅,?.Invoke是安全触发——没有订阅者时不抛异常。object sender和EventArgs e两个参数是 .NET 事件的标准签名,WinForm 控件事件基本都长这样,看懂这一处就能触类旁通看懂大部分控件事件。注意s是触发事件的源对象,强转回Counter就能读取它的状态,这在「一个事件被多个控件订阅」的场景很实用。
3.3 用实例记住 LINQ 查询,而不是背 API
涉及「按条件过滤」「取最大最小」「分组统计」的例子,一般同时给foreach和 LINQ 两个版本。建议都跑一遍,重点理解延迟执行:
List<int> data = new List<int> { 5, 8, 2, 9, 3 }; var odd = data.Where(n => n % 2 == 1); // 此时还没有真正计算 foreach (int n in odd) { Console.WriteLine(n); // 真正执行查询 }Where返回IEnumerable<int>,在枚举之前修改源集合,查询结果可能和直觉不符,这是「例子能跑但看不出门道」的典型点。实例里最常用的五个方法是Where过滤、Select投影、OrderBy排序、FirstOrDefault取单条、GroupBy分组,覆盖了九成场景。FirstOrDefault找不到元素时返回默认值而不是抛异常,适合从列表取一个配置项。
新手容易犯的误用是在循环里反复ToList(),把每次查询结果物化成新集合,既浪费内存又让 GC 压力变大。正确做法是先算一次结果存到变量,再对这个变量做循环。
4. 经典 WinForm 实例里的高频模式:DataGridView、Timer 循环采集与界面刷新
4.1 DataGridView 数据绑定的三步套路
198 个经典实例里,凡和数据展示相关的窗体基本都有 DataGridView。标准套路是三步:构造数据源、绑定、处理单元格事件。
// 1. 构造数据源:DataTable 或 List<T> DataTable dt = new DataTable(); dt.Columns.Add("Id", typeof(int)); dt.Columns.Add("Name", typeof(string)); dt.Rows.Add(1, "PLC-01"); dt.Rows.Add(2, "PLC-02"); // 2. 绑定到网格 dataGridView1.DataSource = dt; dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill; // 3. 单元格点击读取当前行 dataGridView1.CellClick += (s, e) => { if (e.RowIndex < 0) return; // 点表头时 RowIndex 为 -1 string name = dataGridView1.Rows[e.RowIndex].Cells["Name"].Value.ToString(); MessageBox.Show(name); };绑定之后直接改dt.Rows[0]["Name"] = "新值"界面不会自动刷新,需要调dataGridView1.Refresh(),或者在数据源和网格之间加一个BindingSource,修改完调用bindingSource.ResetBindings()。这是实例运行中「数据看着没更新」最常见的根源。
DataGridViewComboBoxColumn在录入型窗体里很常见,它的下拉数据源要设置在列级别,在Columns集合里找到该列后设置DataSource和ValueMember,单元格级别只负责选值。还有一对事件要注意:CellValueChanged不会在下拉框切换选项时立刻触发,要先用CurrentCellDirtyStateChanged事件里调用CommitEdit(),再在CellValueChanged里取新值,两个事件接在一起才完整。
4.2 Timer 做循环数据采集时 UI 刷新卡顿的处理方法
这是 C# 上位机开发里被问得最多的问题。System.Windows.Forms.Timer的 Tick 事件运行在 UI 线程,在 Tick 里做采集再写 TextBox,采集一耗时界面就卡。正确分工是:采集放后台线程,UI 刷新通过Control.BeginInvoke交回界面线程:
private void StartSampling() { var timer = new System.Threading.Timer(_ => { double value = ReadSensor(); // 模拟耗时采集 BeginInvoke(new Action(() => { textBoxValue.Text = value.ToString("F2"); })); }, null, 0, 1000); // 0 表示立即启动,1000 是周期毫秒数 }System.Threading.Timer的四个参数依次是回调委托、状态对象、首次执行延迟毫秒数、执行周期毫秒数。BeginInvoke把委托排队到 UI 线程消息循环异步执行,采集线程不会阻塞;如果用Invoke则是同步等待,高频采集时采集线程会被拖慢。窗口关闭时要Dispose这个定时器,否则回调访问已销毁的控件会抛ObjectDisposedException。
采集频率高于 UI 刷新能力时(比如 50ms 采集一次),更好的做法是加脏标记:采集线程只更新共享字段,UI 用 200ms 的 Forms.Timer 统一把最新值刷到界面。这样 UI 每秒只刷新 5 次,CPU 占用率比每次采集都BeginInvoke低一个量级。
4.3 控件焦点、Tab 顺序与输入校验的常规处理
带录入窗体的实例基本都会涉及焦点和校验。textBox1.Focus()放在Form_Shown事件里比放在Load里可靠,因为Load时控件尚未完全显示。校验常用Validating事件,配合errorProvider提示错误:
private void textBoxPort_Validating(object sender, CancelEventArgs e) { if (!int.TryParse(textBoxPort.Text, out int port) || port < 1 || port > 65535) { errorProvider1.SetError(textBoxPort, "端口号必须是 1-65535 的整数"); e.Cancel = true; // 焦点无法离开此控件 } else { errorProvider1.SetError(textBoxPort, ""); } }CancelEventArgs.Cancel设为 true 会让焦点停在当前控件,实现「不填对就不放行」。但注意在FormClosing里Validating也会触发,导致窗体无法关闭,常见做法是设一个_isClosing标志,关闭时跳过校验。TabIndex决定按 Tab 的焦点跳转顺序,在设计器里逐个设置即可。
5. 实例源码编译不过和运行报错的排查顺序
5.1 目标框架不一致的批量修复
合集里项目年龄跨度大,v4.0、v4.5、v4.6.2 混在一起很常见。逐个改项目属性太慢,用 PowerShell 批量替换全部.csproj效率最高:
Get-ChildItem -Recurse -Filter *.csproj | ForEach-Object { $content = Get-Content $_.FullName -Raw $content = $content -replace '<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>', '<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>' $content = $content -replace '<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>', '<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>' Set-Content $_.FullName $content -Encoding UTF8 }Get-ChildItem -Recurse递归查找子目录里的项目文件,-Raw整文件读取避免编码截断。替换前先复制一份原始目录,因为 VS 重载项目后会改写csproj,改动不可逆。替换后如果某个项目引用了只在更高框架才有的 API,编译会明确提示,到时单独回退那一个项目即可。
5.2 缺失引用与 NuGet 包还原
编译报「命名空间不存在」时,先区分是缺引用还是拼写错误。错误列表里「是否缺少 using 指令或程序集引用」这句话是分水岭:using标红但项目里能搜到同名类,说明只是少了using指令;搜不到同名类,才是引用缺失。实例里常见的Newtonsoft.Json、NModbus4属于第三方包,解决方案上有「还原 NuGet 程序包」动作,离线环境则要手动添加 DLL 引用,前提是 DLL 已在合集目录里。
5.3 运行期高频异常与对应处理
| 异常信息 | 出现场景 | 处理方式 |
|---|---|---|
NullReferenceException | 控件未初始化就访问、DataGridView 取到空行 | 访问前判空,检查对象是否真的被赋值 |
InvalidOperationException: Cross-thread... | 后台线程直接改 UI 控件 | 改用BeginInvoke或Invoke切回 UI 线程 |
IndexOutOfRangeException | 数组或字符串长度不足 | 访问前检查Length,优先用TryGetValue |
ObjectDisposedException | 窗体关闭后后台回调访问控件 | 定时器Dispose,回调里判断IsDisposed |
FormatException | 用int.Parse解析非法输入 | 换成int.TryParse先解析再判断 |
跨线程访问控件是最让初学者困惑的一条。要理解Invoke是同步调用,会等 UI 线程执行完才返回,高频采集下会让采集线程阻塞;BeginInvoke是异步排队,不会等。实例里写Invoke能跑通,但在高频率场景会掩盖一个性能问题。
运行期异常还有一个规律值得注意:第一次运行正常、第二次崩溃,通常指向静态字段状态残留或全局事件没退订,比如窗体关闭但静态事件还引用着旧实例,下次打开时事件触发两次。处理方式是在Dispose里把+=订阅的事件用-=退订。
6. 用功能模块归类源码,沉淀出自己的 WinForm 代码片段库
6.1 把 298 个例子整理成可检索的功能索引
把全部例子从头到尾跑一遍不现实,也没必要。我一般会把合集目录整理成一张索引表,记录每个实例的功能关键词、项目路径、核心代码段、可复用点:
| 功能关键词 | 项目路径 | 核心代码段 | 可复用点 |
|---|---|---|---|
| 串口收发 | SerialPortDemo | sp.DataReceived += ... | 上位机通信模板 |
| DataGridView 分页 | GridViewPager | BindingSource.Filter | 列表分页基础 |
| 窗体淡入淡出 | FadeForm | Opacity属性动画 | 界面美化基础 |
这张表不用一次建完,遇到问题翻源码时顺手补一行。三个月后,合集就变成了你自己的检索工具——记住它的合理用法是「按需查」,不是「从头看」。
6.2 提取公共方法类而不是复制粘贴
实例里重复出现的串口数据解析、CRC 校验、配置读写,是复用价值最高的部分。跑通一个例子后,把工具方法抽成静态类放进公共项目,按「输入输出清晰、无界面依赖、无状态」三个标准筛选:
public static class ByteHelper { /// <summary>将 byte[] 转为十六进制字符串,便于串口日志输出</summary> public static string ToHexString(byte[] data) { return BitConverter.ToString(data).Replace('-', ' '); } }注意不要在公用方法里访问MessageBox或具体窗体控件,否则复用性会迅速下降。ToHexString这类纯函数放哪里都能用,这是和页面代码解耦的基本线。
6.3 用一个最小 WinForm 骨架固定学习成果
最后建议把高频模式收敛成一个最小启动模板:一个主窗体、一个System.Threading.Timer做后台任务、BeginInvoke刷新界面、一个 DataGridView 绑定数据源、一套TryParse加errorProvider的校验框架。后续新项目都从这份骨架开始,例子里学到的控件、事件、模式按需嵌入,那 298 个实例就真正从别人的代码变成了你自己的工程习惯。
本文还有配套的精品资源,点击获取