简介:本资源是面向C#初学者与进阶开发者的系统性实践学习包,聚焦语法基础、面向对象编程、异常处理、事件委托、LINQ查询、文件I/O及async/await异步编程等核心能力训练,通过100个典型实例打通从概念理解到代码落地的关键环节。压缩包共1370个文件,以200个.cs源码文件为主体,辅以166个可执行exe(用于快速验证效果)、64个.csproj与66个.sln工程文件(支持VS直接加载调试)、110个.bmp图形资源及配套.resx本地化资源、.pdb调试符号等,结构完整,便于分模块研读与运行调试;整体体积仅3.49MB,轻量易下载。目前已有129人学习下载。每个实例均对应明确知识点,如封装类设计、try-catch异常链处理、LINQ集合查询、XML文件读写、委托回调机制实现等,代码规范、注释清晰,适合作为日常编码参考、课堂实训素材或自学复盘范本。
1. C#程序100实例:不是题库,是能直接粘进VS跑通的「可执行模块包」
你手头那份标着“C#程序100实例”的压缩包,大概率不是PDF习题集,也不是PPT课件——它极可能是某位一线开发者在带新人时拆出来的「最小可运行单元集合」:每个实例都是独立.cs文件,含完整Main()入口,不依赖外部NuGet包(或仅用System系基础命名空间),编译即跑、运行即见结果。它解决的不是“怎么学语法”,而是“写完Hello World后,下一步该敲什么才能不卡在Console.ReadLine()卡死、数组越界报错、委托不会声明、LINQ查不出数据”这些真实翻车点。适合刚过完《C#入门经典》前六章、对着MSDN文档发懵的初级开发者,也适合需要快速验证某个语言特性(比如async/await在控制台下的行为边界、Span<T>和数组性能差多少)的老手当「后悔药」用。这不是教学大纲,是压舱石——你不需要从头学起,但每次遇到不确定,都能在里面找到一个能立刻编译、调试、改参数、看输出的参照物。
2. 实例结构解剖:为什么这100个文件能直接进Visual Studio跑起来
2.1 文件组织逻辑:按「能力断层」而非「语法章节」分组
这100个实例绝非按“变量→循环→类→继承→泛型”线性排列。实际目录结构更接近实战调试路径:
01_BasicIO:专注Console.WriteLine格式化陷阱(如{0:C}在不同Culture下输出差异)、ReadLine()读空行导致后续逻辑跳过、TryParse失败却不处理返回值;03_CollectionEdgeCases:专门暴露List<T>.RemoveAt(10)越界不报错但静默失败、Dictionary<TKey, TValue>键重复时Add()抛异常而[]=赋值不抛、foreach中修改集合引发InvalidOperationException;07_AsyncConsole:所有async Main()示例都强制加await Task.Delay(1)防窗口闪退,且每个都带try-catch AggregateException捕获Task.Run内未处理异常——这是新手最常漏的黑匣子。
提示:不要试图按数字顺序学。先定位你当前卡点(比如“List删除元素总少一个”),直接搜
CollectionEdgeCases文件夹,打开对应.cs,F5运行,再逐行设断点看Count变化时机。
2.2 源码级可运行设计:零配置、零依赖、零玄学
每个.cs文件顶部都有统一注释块,明确标注其最小运行环境要求:
// [C# Version] 8.0+ (需启用Nullable Reference Types) // [Target Framework] netcoreapp3.1 (兼容net5.0) // [Dependencies] 仅 System, System.Collections.Generic, System.Linq // [Run Command] dotnet run --project .\Example023_ListRemove.csproj关键点在于:它不生成传统.csproj项目文件,而是用dotnet new console -o Example023_ListRemove创建空壳后,把.cs文件直接拖进Program.cs位置覆盖,再用dotnet run命令启动。这意味着你无需安装Visual Studio——只要装了.NET SDK,打开终端进文件夹,一行命令就能跑:
# 假设你已cd到实例根目录 dotnet new console -n Example023_ListRemove cd Example023_ListRemove # 将原始Example023_ListRemove.cs内容复制到Program.cs中(覆盖) dotnet run这个设计规避了新手最头疼的“项目文件配置错误”:不用管<OutputType>是否为Exe,不用查<TargetFramework>拼写,连<LangVersion>都不用显式指定——因为所有实例代码都严格限定在C# 8.0语法内(避免using declarations等新特性导致旧SDK报错)。
2.3 输入/输出契约:每个实例自带「预期结果校验」
不同于教科书只写“输出结果如下”,这些实例在Main()末尾强制加入断言式校验:
// Example045_LINQGroupBy.cs 片段 var result = products.GroupBy(p => p.Category).Select(g => new { Category = g.Key, Count = g.Count() }); Console.WriteLine($"Category 'Electronics' count: {result.First(x => x.Category == "Electronics").Count}"); // ↓ 关键校验行:防止LINQ查询为空时First()抛异常 Debug.Assert(result.Any(x => x.Category == "Electronics"), "Electronics category not found in grouping");这种写法强迫你关注数据源真实性:如果products列表为空,First()会直接崩溃,而Assert会在调试模式下弹窗提示,比NullReferenceException堆栈更直指问题根源。这也是为什么它能当“后悔药”——你改了代码后,只要Assert不炸,就说明核心逻辑没崩。
3. 快速上手四步法:从解压到调试只需5分钟
3.1 步骤一:环境检查与最小化安装
别急着解压!先确认你的.NET SDK版本是否匹配实例要求(多数实例基于netcoreapp3.1或net5.0):
# Windows/macOS/Linux 统一命令 dotnet --list-sdks # 输出示例: # 3.1.426 [/usr/share/dotnet/sdk] # 5.0.408 [/usr/share/dotnet/sdk] # 6.0.302 [/usr/share/dotnet/sdk]若无3.1.x或5.0.x,请去 dotnet.microsoft.com/download 下载对应SDK(注意选SDK而非Runtime)。安装后重启终端,再次运行dotnet --list-sdks确认。
注意:Visual Studio 2022默认捆绑.NET 6+ SDK,但若你用的是VS Code,必须单独安装SDK——VS Code本身不带任何.NET运行时。
3.2 步骤二:解压与目录映射
解压后你会看到类似结构:
CSharp100Examples/ ├── 01_BasicIO/ │ ├── Example001_HelloWorld.cs │ └── Example002_ReadInput.cs ├── 02_StringManipulation/ │ ├── Example003_StringSplit.cs │ └── Example004_RegexMatch.cs └── ...关键动作:将整个CSharp100Examples文件夹拖入VS Code(推荐)或Visual Studio。VS Code用户需安装C#扩展(由OmniSharp提供),VS用户无需额外操作。此时不要双击任何.cs文件——先打开终端(Ctrl+),cd进任一子文件夹(如01_BasicIO`)。
3.3 步骤三:单文件编译运行(绕过项目文件)
以Example001_HelloWorld.cs为例,它本质是独立脚本,无需.csproj:
# 进入01_BasicIO目录后执行 dotnet new console -n HelloWorldTemp cd HelloWorldTemp # 将Example001_HelloWorld.cs内容全选复制,粘贴覆盖Program.cs dotnet run输出应为:
Hello, World! Press any key to continue...若报错The type or namespace name 'Console' could not be found,说明using System;被误删——检查Program.cs顶部是否含此引用。所有实例均以此为底线,缺则必报错。
3.4 步骤四:调试器介入:让“为什么没输出”变成“哪一行没执行”
在VS Code中打开Program.cs,点击行号左侧设断点(红点),按F5启动调试。观察左下角“变量”面板:
args数组长度是否为0(验证命令行参数传递);Console.ReadLine()返回值是否为null(输入流关闭时);DateTime.Now.ToString("yyyy-MM-dd")结果是否符合预期(Culture影响)。
血泪经验:新手常以为
Console.ReadKey()会暂停程序,却忽略它返回ConsoleKeyInfo对象而非字符串——在Example002_ReadInput.cs中,你将看到如何正确提取key.KeyChar并转成string,避免ToString()输出"KeyChar: A"这种玄学字符串。
4. 避坑指南:100个实例里埋着的5个高频翻车点
4.1 现象:Example033_ThreadSleep.cs运行后控制台瞬间关闭,看不到输出
原因:Thread.Sleep(1000)只让主线程休眠1秒,但Main()结束后进程立即退出,Console.WriteLine()缓冲区未刷新。
解决:在Sleep后加Console.ReadKey()或Console.WriteLine("Press any key..."); Console.ReadKey();。更规范做法是用Task.Delay(1000).Wait()替代Thread.Sleep,因Task在Main()结束前会阻塞退出。
4.2 现象:Example057_DictionaryAdd.cs中dict.Add("key", "value")抛ArgumentException,但dict["key"] = "value"正常
原因:Add()方法要求键必须不存在,而[]=赋值会覆盖旧值。实例中故意初始化dict后立即Add相同键两次。
解决:先用dict.ContainsKey("key")判断,或改用dict.TryAdd("key", "value")(C# 7.0+),失败时返回false不抛异常。
4.3 现象:Example089_LINQOrderBy.cs排序结果与预期相反(如数字升序变降序)
原因:OrderBy(x => x)默认升序,但若x是字符串,会按Unicode码点排序("10" < "2"),而非数值大小。
解决:对数字字符串先int.Parse()再排序:list.OrderBy(x => int.Parse(x)),或用OrderBy(x => Convert.ToInt32(x))。实例中Example089已预埋此坑,需你手动修复。
4.4 现象:Example095_AsyncAwait.cs中await Task.Run(() => {...})内Console.WriteLine无输出
原因:Task.Run在后台线程执行,而Console是同步I/O,多线程写入可能乱序或丢失。
解决:用Console.Out.WriteLine替代Console.WriteLine,或在Task.Run内加lock(Console.Out)同步写入。实例中已用lock方案,但需你理解为何要锁Console.Out而非Console。
4.5 现象:Example100_FileStreamWrite.cs写入文件后,用记事本打开显示乱码(中文变问号)
原因:FileStream默认无编码,StreamWriter未指定Encoding.UTF8,Windows记事本用ANSI打开UTF-8文件。
解决:创建StreamWriter时强制指定编码:new StreamWriter(fileStream, Encoding.UTF8)。实例中Example100已修正,但若你删掉Encoding.UTF8参数就会复现此坑。
5. 进阶技巧:把单实例升级为可批量验证的测试套件
5.1 为什么需要批量验证?
当你修改了某个通用工具方法(如StringHelper.Truncate(string, int)),需要确保100个实例中所有调用它的案例仍通过。手动逐个运行效率太低,且易遗漏。此时需将实例转化为可自动执行的测试用例。
5.2 构建轻量测试框架:用dotnet test驱动实例
第一步:在项目根目录创建TestRunner.cs,定义统一测试接口:
// TestRunner.cs public interface IExampleTest { string Name { get; } void Execute(); bool ValidateOutput(string actualOutput); } // 所有实例需实现此接口,例如Example001_HelloWorld.cs改为: public class Example001_HelloWorld : IExampleTest { public string Name => "HelloWorld"; public void Execute() => Console.WriteLine("Hello, World!"); public bool ValidateOutput(string actualOutput) => actualOutput.Contains("Hello, World!"); }第二步:编写测试发现器(TestDiscoverer.cs):
// 自动扫描所有IExampleTest实现类 public static class TestDiscoverer { public static IEnumerable<IExampleTest> GetAllTests() { var assembly = Assembly.GetExecutingAssembly(); return assembly.GetTypes() .Where(t => t.IsClass && !t.IsAbstract && t.GetInterfaces().Contains(typeof(IExampleTest))) .Select(t => (IExampleTest)Activator.CreateInstance(t)); } }第三步:在Program.cs中集成批量执行:
// Program.cs var tests = TestDiscoverer.GetAllTests(); var results = new List<(string name, bool passed, string output)>(); foreach (var test in tests) { var writer = new StringWriter(); Console.SetOut(writer); // 重定向输出 try { test.Execute(); var output = writer.ToString(); var passed = test.ValidateOutput(output); results.Add((test.Name, passed, output)); } catch (Exception ex) { results.Add((test.Name, false, $"Exception: {ex.Message}")); } } // 输出汇总报告 Console.WriteLine($"Passed: {results.Count(r => r.passed)}/{results.Count}");5.3 参数化测试:同一实例跑多组输入
以Example045_LINQGroupBy.cs为例,原代码用硬编码products列表。改造为支持参数注入:
public class Example045_LINQGroupBy : IExampleTest { private readonly List<Product> _products; public Example045_LINQGroupBy(List<Product> products = null) { _products = products ?? new List<Product> { new Product { Name = "Laptop", Category = "Electronics" }, new Product { Name = "Book", Category = "Education" } }; } // ... Execute() 和 ValidateOutput() 方法 }然后在测试运行器中传入不同数据集:
var testWithEmpty = new Example045_LINQGroupBy(new List<Product>()); var testWithDuplicates = new Example045_LINQGroupBy( Enumerable.Repeat(new Product { Name="A", Category="X"}, 100).ToList());这样,一个实例就覆盖了空集合、大数据量、重复键等边界场景。
5.4 输出对比自动化:用Diff工具验证修改影响
当你要优化Example077_SortAlgorithm.cs的冒泡排序为快速排序时,需确保输出完全一致。在ValidateOutput中加入快照比对:
public bool ValidateOutput(string actualOutput) { var expectedPath = $"snapshots/{Name}_expected.txt"; if (!File.Exists(expectedPath)) { File.WriteAllText(expectedPath, actualOutput); // 首次运行存快照 return true; } var expected = File.ReadAllText(expectedPath); return expected == actualOutput; // 严格字符匹配 }每次运行后,若输出变更,测试失败并提示“快照不匹配”,迫使你确认是功能增强还是逻辑错误。
从那以后我每次重构一个实例,都强制走一遍dotnet test批量验证流程,哪怕只改了一行for循环条件。因为100个实例不是100个练习,而是100个契约——它们共同定义了“这段C#代码应该长什么样”。希望帮到你。
本文还有配套的精品资源,点击获取