简介面向C#初学者设计的基础练习题合集覆盖基本语法与数据类型、控制结构、函数与方法、数组和集合、类与对象、接口与抽象类、异常处理、枚举与委托、文件与流、LINQ查询、异步编程、泛型等12个核心知识点通过大量可运行示例引导读者从理论走向实战。压缩包内共1670个文件以224个cs源代码文件为主体并包含82个sln/94个csproj工程文件方便直接编译打开164个exe可执行程序可直观查看运行结果另有config配置、resources资源及jpg/ico界面素材等辅助文件整体压缩后仅17.87MB轻量易用。练习项目覆盖控制台与Web两种场景适合在Visual Studio中逐步调试借助PDB符号文件跟踪代码执行路径还可参看aspx/asax等Web文件理解前后端交互。目前已有729人学习下载是笔试备考、课程设计或自学巩固C#基础时值得收藏的练手资源。1. 一套能直接跑起来的C#基础练习题从语法、面向对象到单元测试全覆盖字符串统计、订单计价、文件日志读取、LINQ分组……这些就是C#基础练习题包里的典型题目。我把这套练习完整跑过一遍感受到它和其他练习资源的明显差异每道题不是孤立的几句话而是带完整工程结构的可运行代码。你打开解决方案编译、运行、对照答案解析再跑一遍配套的单元测试整个过程就像拆一个真实的生产项目。它适合三类人刚学完C#语法想动手写代码的入门者、准备初级开发面试的求职者、需要给团队新人做上岗训练的导师。这套题覆盖语法基础、面向对象、集合与LINQ、异常与调试、文件操作五个阶段由浅入深每一阶段都按真实项目的组织方式展开。2. 整体架构与组织方式一套分阶段练习包的目录与运行约定2.1 分阶段的题目组织语法、面向对象、进阶与综合这套C#基础练习题把大约三十道题目分成四个阶段整体难度呈阶梯式上升。第一阶段是C#语法基础覆盖变量声明、类型转换、分支、循环、数组和字符串处理比如用循环找水仙花数、用switch实现简易计算器第二阶段是面向对象核心覆盖类与对象、属性、构造器、继承、多态、抽象类和接口比如设计一个汽车继承体系、实现一个支持折扣计算的订单系统第三阶段是进阶主题覆盖泛型集合、LINQ查询、委托与事件、异常处理比如用Dictionary统计词频、用Lambda表达式筛选数据第四阶段是综合实战把前面阶段的知识点串起来做一个带文件读写的小型日志分析程序。每题的设计思路是一致的先给题目说明文档再给一个可运行的代码框架最后是参考答案。参考答案不是简单堆代码而是有两种形态——第一种是完整代码直接复制到对应文件里就能运行第二种是单元测试测试会调用你写的类或方法用断言检查输出结果。这种设计逼着你不只是把代码跑通而是让你的代码满足确定的输入输出规范。2.2 技术栈与依赖.NET 8控制台应用搭配xUnit测试项目整个练习解决方案只依赖两个基础技术.NET 8控制台应用和xUnit测试框架。控制台应用用来跑题目xUnit用来验证答案没有引入任何第三方业务库因为练习题的核心价值在于C#语言本身。我拆包时看到的解决方案组织方式是这样的CSharpExercises.sln ├── src/ # 全部题目工程 │ ├── 01_Basics/ # 语法基础阶段 │ ├── 02_OOP/ # 面向对象阶段 │ ├── 03_Advanced/ # 进阶主题阶段 │ └── 04_Comprehensive/ # 综合实战阶段 ├── tests/ │ └── ExerciseTests/ # 配套的xUnit测试项目 ├── README.md # 总说明与运行指引 └── global.json # 锁定SDK版本src 下面每个题目的代码文件我一般称之为“题目标题.cs”测试项目里则是对应的“题目标题Tests.cs”。一个阶段对应一个子解决方案目录每个题目有独立的工程这样做的最大好处是某一道题的编译错误不会拖垮其他题目新人练习时也不会被乱七八糟的项目依赖吓到。2.3 单题的目录结构每个题目自带说明、模板与参考答案拆开其中一道题结构是这样的02_OOP/ └── 006_OrderSystem/ ├── OrderBase.cs # 抽象基类模板 ├── NormalOrder.cs # 普通订单模板待补充 ├── VipOrder.cs # VIP订单模板待补充 ├── Program.cs # 入口构造订单并输出折扣结果 ├── 题目说明.md # 题目要求、输入输出示例 └── 参考答案.cs # 完整答案放在独立文件方便对照模板文件里预留了方法和属性的签名注释里写清楚了什么地方需要补充代码。参考代码单独放在一个文件里而不是直接贴在模板下方这样练习时就不会一打开文件就瞟到答案。每个题目的 Program.cs 都是独立入口用命令行 dotnet run --project 指向具体题目的工程文件就能单独跑一道题。这个结构本身就是一套脚手架题目说明相当于需求文档模板文件相当于接口定义参考答案则是实现。按这种结构去读题、写代码跟平时进公司接一个需求再交付实现的工作流是一样的这也是我把这套练习题推荐给带新人的导师的原因。3. 核心模块拆解四道代表题从题目说明到答案验证3.1 语法阶段用Dictionary统计字符频率第一阶段的代表题是字符频率统计题目要求读入一段英文短文忽略大小写统计每个英文字母出现的次数。很多新手会先想到两层循环外层遍历字母表内层遍历字符串这么做也能跑但时间复杂度是 O(n×26)。参考答案用的是一次遍历加 Dictionary 的方式using System; using System.Collections.Generic; string input Hello World, C# Exercises!; var stats new Dictionarychar, int(); foreach (char rawChar in input) { char c char.ToLowerInvariant(rawChar); // 统一转小写忽略大小写 if (!char.IsLetter(c)) // 只统计字母跳过空格和标点 continue; if (stats.TryGetValue(c, out int count)) stats[c] count 1; // 已存在则计数加一 else stats[c] 1; // 第一次遇到则写入初始值1 } foreach (var pair in stats) Console.WriteLine(${pair.Key}: {pair.Value});这里值得拎出来讲的是TryGetValue的用法。新手常见写法是先ContainsKey再索引取值等于在原字典里查了两遍TryGetValue一次查出结果out int count直接带出旧值再stats[c] count 1完成更新代码少写一行性能也更好。char.IsLetter是过滤条件确保标点和空格不会混进统计结果。Console.WriteLine的字符串插值在 .NET 6 之后是推荐写法比老的{0}占位符可读性强很多。运行时传参不是必须的这个程序默认硬编码了输入文本。如果你想练命令行参数可以在Main方法里读args[0]。但练习包里更推荐的做法是让用户手动输入把input改成Console.ReadLine()让每次运行都能换一段文本测试。这题的用意就是让新手意识到Dictionary不是只在写业务系统时才用得上基础题里同样能体会数据结构对代码质量的影响。3.2 面向对象阶段用订单体系练抽象类与多态第二阶段有一道订单计价题抽象类的用法在这里体现得很清楚。题目给了一个OrderBase抽象基类里面定义了订单号、金额和计算折扣的抽象方法然后要求你写两个子类public abstract class OrderBase { private decimal _amount; public string OrderId { get; set; } string.Empty; // 自动属性简短赋值 public decimal Amount { get _amount; set { if (value 0) throw new ArgumentException(订单金额不能为负数); _amount value; } } public abstract decimal CalculateDiscount(); // 抽象方法子类必须实现 } public class NormalOrder : OrderBase { public override decimal CalculateDiscount() Amount * 0.05m; // 普通会员95折 } public class VipOrder : OrderBase { public decimal ExtraDiscount { get; set; } 0.10m; public override decimal CalculateDiscount() Amount * (0.05m ExtraDiscount); // VIP叠加折扣 }OrderBase里Amount属性的 set 访问器做了参数校验负金额直接抛异常这是真实项目里很常见的防御性编程写法。CalculateDiscount是抽象方法子类必须override这就是面向对象里“约定”的含义基类规定了行为接口子类决定具体实现。NormalOrder和VipOrder的折扣算法不同但调用方的代码完全不用改这就是多态的价值。这题练习的重点一是属性校验怎么写二是继承体系怎么建三是在Program.cs里用基类引用来统一调用ListOrderBase orders new() { new NormalOrder { OrderId NO01, Amount 1000m }, new VipOrder { OrderId VO01, Amount 1000m, ExtraDiscount 0.15m } }; foreach (var order in orders) Console.WriteLine(${order.OrderId} 应付: {order.Amount - order.CalculateDiscount():C});ListOrderBase存储的是不同子类实例调用CalculateDiscount时运行时会根据真实类型分发到对应实现这就是多态。:C格式说明符会把数字输出成带货币符号的字符串。练习配套的测试会模拟这两个订单子类分别断言普通订单输出95折、VIP订单输出85折这样参考答案对不对就一目了然。3.3 进阶阶段LINQ分组统计与集合操作LINQ 是这套练习题里最能体现 C# 风格的语法。进阶阶段有一道销售记录分析题给一个ListSaleRecord要求按产品名称分组统计每个产品的总销量并输出前三位。参考答案是这样的using System; using System.Collections.Generic; using System.Linq; var sales new ListSaleRecord { new(显示器, 5), new(键盘, 8), new(显示器, 3), new(鼠标, 12), new(键盘, 4) }; var top3 sales .GroupBy(s s.ProductName) // 按产品名分组 .Select(g new // 投影出统计结果 { Product g.Key, Total g.Sum(s s.Quantity) // 组内数量求和 }) .OrderByDescending(x x.Total) // 按总量降序 .Take(3); // 取前三名 foreach (var item in top3) Console.WriteLine(${item.Product}: {item.Total});.GroupBy返回一个IGroupingstring, SaleRecord的集合g.Key就是分组依据g.Sum(s s.Quantity)对组内每条记录的Quantity求和。Select把分组结果投影成匿名对象匿名类型在 LINQ 查询中很常用不用专门为此定义一个类。OrderByDescending排序Take(3)截断前三条这条查询链在语义上就是一段“自然语言”式的数据处理流程。换成传统for循环加字典来做代码量至少多一倍还得自己处理“键不存在”的边界情况。这道题的目的就是让练习者感受到 LINQ 对集合处理的表达力。测试里会构造多组销售数据说“如果排错名次就失败”逼着你把排序逻辑抠到每一环。3.4 综合阶段异常处理与文件日志读取最后一道综合大题是文件日志分析要求读取一个多行文本日志文件过滤出 ERROR 级别的行并输出。这题把文件 API、集合筛选和异常处理串在了一起using System; using System.Collections.Generic; using System.IO; using System.Linq; string logPath C:\logs\app_2025.log; try { if (!File.Exists(logPath)) // 文件不存在时提前返回 { Console.WriteLine(日志文件不存在请检查路径); return; } IEnumerablestring errors File .ReadLines(logPath) // 惰性逐行读取不一次性载入内存 .Where(line line.Contains([ERROR], StringComparison.OrdinalIgnoreCase)) .Select(line line.Substring(line.IndexOf(]) 1).Trim()); // 去掉级别前缀 foreach (string error in errors) Console.WriteLine(error); } catch (UnauthorizedAccessException ex) // 权限不足时给出友好提示 { Console.WriteLine($无法读取文件可能权限不足{ex.Message}); } catch (IOException ex) // 文件被其他进程占用等IO异常 { Console.WriteLine($读取文件时发生IO错误{ex.Message}); }File.ReadLines和File.ReadAllLines的区别值得注意ReadAllLines一次性把整个文件读入内存日志文件大时会明显卡顿ReadLines是惰性读取配合Where在遍历过程中一边读一边过滤内存占用小很多。StringComparison.OrdinalIgnoreCase是字符串比较的标准写法不写这个参数默认是区分大小写的新手经常会在这个细节上翻车。异常处理分成两个catchUnauthorizedAccessException和IOException分别提示比笼统的catch (Exception)对排障更有帮助。练习包里这题的测试代码相对简洁它会用临时目录生成一个测试日志文件塞入若干行信息然后断言返回结果里只包含 ERROR 行。最后一题能完整通过测试基本说明你对控制台程序、文件操作、LINQ 和异常处理都有了可落地的认识。4. 避坑与常见问题排查运行这套练习题时最容易翻车的四个点4.1 控制台窗口一闪而过答案没看到就退出了现象dotnet run后程序看起来什么都没输出或者窗口直接关闭。原因多数情况下不是代码写错而是Program.cs里的Main方法入口优先级被改变了——某些模板文件在粘贴时把题目逻辑写进了顶级语句区或者用了await后没有正确处理异步上下文。解决先看输出窗口的滚动记录再确认Program.cs里只有一个入口点。如果只是“输出太快看不到”在Main末尾加一句Console.ReadKey()让控制台等待按键这是最简单且保真的排查方法。4.2 测试项目一直报找不到某个类或命名空间现象dotnet test运行时报 CS0246 或 CS0234 错误说找不到OrderBase或某个目标命名空间。原因测试项目没有正确引用主项目或者引用了同名但不同路径的项目。解决按顺序执行下面两条命令确认引用关系dotnet add tests/ExerciseTests/ExerciseTests.csproj reference src/02_OOP/006_OrderSystem/006_OrderSystem.csproj dotnet restore如果用的是 IDE就在测试项目的“依赖项”里手动添加项目引用。这里最容易翻车的点是把测试项目引用成了别的题目工程因为每个阶段都有多个工程复制粘贴命令时看花眼。4.3 字符串比较“明明一样”却进不了 if 分支现象判断用户输入是否为“exit”时输入 exit 结果程序没退出。原因if (input exit)在默认情况下是区分大小写的用户输入了Exit或EXIT自然匹配不上。更隐蔽的情况是输入结尾带了换行或空格用了Console.ReadLine()后没有Trim()。解决统一用input.Trim().Equals(exit, StringComparison.OrdinalIgnoreCase)既去掉首尾空白又忽略大小写。这套练习题里有几处字符串匹配题我把这条排进最容易遇到的坑没有任何争议。4.4 改了参考答案运行测试结果却和之前一模一样现象把NormalOrder的折扣改成 0.1m测试结果还是 0.05 的输出。原因测试项目引用的是编译产物 DLL而不是源文件改了源代码没有重新编译测试跑的是旧的二进制。解决先dotnet build CSharpExercises.sln再dotnet test。在 IDE 里就是按一下“重新生成解决方案”。这个问题的迷惑性在于第一次运行测试是正常的改完代码后反而“失灵”其实是构建顺序的问题。从那以后我每次改完答案都会习惯性地看一眼输出窗口的“生成成功”提示再跑测试。5. 运行与调试用命令行把整套练习题完整跑起来5.1 全局编译先让解决方案整体通过再细看单题整套练习题用命令行跑起来非常直接。先进入解决方案所在目录执行dotnet build CSharpExercises.slndotnet build会编译 src 下所有题目工程和 tests 下的测试工程。第一次跑可能会提示还原 NuGet 包解决方案里没有第三方依赖还原出来的只有 xUnit 相关包速度很快。看到Build succeeded就说明全部题目代码至少能过编译关代码水平的差距在运行和测试阶段才会体现出来。编译成功后再逐题运行。比如跑字符统计题dotnet run --project src/01_Basics/001_CharFrequency/001_CharFrequency.csproj--project参数指定了要执行的工程文件不加这个参数时dotnet run只在当前目录下找工程而解决方案根目录下没有默认启动项所以直接运行十有八九会报找不到可执行项目。5.2 运行配套单元测试让答案变成可自动验证的工程每一道题都有对应的测试全局跑测试用一条命令dotnet test tests/ExerciseTests/ExerciseTests.csproj如果只想跑某一道题的测试可以在--filter后面加过滤器dotnet test tests/ExerciseTests/ExerciseTests.csproj --filter FullyQualifiedName~CharFrequency--filter支持按测试名、类名、命名空间过滤FullyQualifiedName~CharFrequency表示匹配测试全名里包含 CharFrequency 的所有用例。测试用例在验证时会用到三类断言模板里长这样using Xunit; public class CharFrequencyTests { [Fact] public void Stats_IgnoresCaseAndPunctuation() { var result CharFrequencyHelper.Count(Hello World!); Assert.Equal(3, result[l]); // 断言具体值 Assert.True(result.ContainsKey(h)); // 断言条件成立 Assert.DoesNotContain(!, result.Keys); // 断言集合不包含某元素 } }Assert.Equal(3, result[l])验证字母 l 出现三次Assert.DoesNotContain验证标点没有被统计进去。测试跑完会显示Passed!或Failed!以及失败详情输出里的堆栈信息会直接跳到断言失败的那一行这是定位逻辑错误的最短路径。5.3 单步调试在IDE里逐行看执行过程练习时遇到逻辑错误最直观的排查方式是单步调试。在 IDE 里打开解决方案把断点打在Program.cs的赋值或循环行上按 F5 启动调试程序会在断点位置停下来。这时候能做三件有价值的事一是把鼠标悬停在变量上查看值二是在“监视”窗口手动输入stats查看字典内容三是在“即时窗口”里输入stats.Count直接执行表达式实测表达式结果。这些操作比满世界Console.WriteLine打印调试信息高效得多也更接近真实项目的排障方式。5.4 批量跑题写完一个阶段统一验证我的习惯是一个阶段写完再统一跑测试。语法基础阶段有十道题一道一道用--filter跑太慢了直接运行整个测试项目一次把全部题的断言全部检查完结果一目了然。这套练习题把答案验证全部做成了测试这一点其实是整个练习包里最值钱的设计——它把一个“做完题自己猜”的玄学过程变成了“运行一次就知道对不对”的确定性工程。只要测试是绿的状态就说明这个阶段的练习目标已达成。6. 二次开发思路把固定题库改造成自己的练题系统6.1 按模板快速新增一道题照着现有体系加新题并不难。第一步在src/03_Advanced/下复制一个现有题目文件夹改成新题目的名字第二步把里面所有类名和命名空间批量替换成新题名第三步在tests/ExerciseTests/下新增一个测试文件按同样的断言风格写三到五个用例第四步在README.md的对应阶段下面加一行题目链接。复制模板省去建工程的步骤替换命名空间一定要全局操作漏掉一个namespace文件会导致测试项目引用错类。6.2 加入难度标签与进度记录我一般会给每道题加一个Meta.json记录阶段、题号、难度和建议用时{ id: 3-011, stage: Advanced, title: LINQ分组统计, difficulty: Medium, suggestedMinutes: 20 }然后在根目录写一个简单的读取控制台按困难程度筛选题目自动拼出“今天练哪几道题”的执行列表。这样整套练习题就从固定顺序变成了可以按需组卷的题库。6.3 考试模式与答案剥离做团队新人验收时我会把所有参考答案.cs文件从工程里排除再把Meta.json里的答案可见性字段标成 false新人在规定时间内独立完成题目最后统一跑测试机。测试用例里可以额外增加时间断言或性能断言比如统计方法必须在 100 毫秒内完成这样不合理的暴力循环自然就会被拦截下来。我从这上面得到的教训是练习题的工程化程度决定了训练效果的确定性。一个能编译、能运行、能自动验证的题目包比一段段零散的示例代码可靠得多。从那以后我每次在本地搭新的练习项目都强制走一遍同样的流程建解决方案、分阶段建目录、写题目说明、补单测。希望帮到你。本文还有配套的精品资源点击获取