简介:面向C#初学者与希望巩固语法基础的开发者,这套习题资源用大量可运行示例覆盖从基本数据类型到泛型、LINQ、异步编程等核心知识点,既能配合教材逐章练习,也可作为面试前快速回顾的题库。压缩包共1670个文件,以224个C#源码文件为主体,另有164个exe可执行程序、172个pdb调试文件及多个资源文件与项目工程文件,整体约17.87MB,解压后即可运行核对。资源已有725人学习,范围涵盖基本语法、控制结构、函数与数组、类与对象、接口与抽象类、异常处理、委托与事件、文件流、LINQ查询及async/await异步方法。通过修改和调试这些实例代码,能深入理解值传递与引用传递、封装继承多态、集合操作、文件读写等易混淆概念,形成面向对象编程思维。从近百个示例工程来看,这套材料以教学实例为导向,适合零基础者在短时间内完成C#入门训练。 很多人在学C#的时候都有一种错觉:看视频、抄代码都挺顺,一关掉教程自己写,马上卡壳。尤其那些冲着“C#上位机开发”“机器视觉”来的朋友,往往一上来就想去碰Socket、串口、SDK,结果连字符串处理都写不利索,最后被各种小报错磨掉耐心。市面上“C#基础练习题”这类资料其实不少,但大多停留在“求1到100的和”“判断闰年”这种为做题而做题的层次,跟真实开发场景脱节。我这篇不打算再给你列一堆教科书题,而是把练习按“实际项目里一定会用到”的标准重新组织一遍——每道题背后都对应一个具体需求场景。你如果能把这套题刷明白,再去碰上位机、Web API、Halcon联合编程这些东西,底气会完全不一样。
1. 开始之前:环境配置里最容易绊倒新手的三个坑
既然要做练习,第一个要解决的是“代码在哪跑”的问题。很多新手上来就装了个Visual Studio,然后照着老教程写代码,结果控制台程序跑起来黑窗口一闪而过,或者压根连编译都过不了。这里说三个我见过的、出现频率极高的环境坑。
1.1 版本选不对,教材里的代码就是跑不起来
现在微软官方主推的版本是.NET 6/7/8,如果你去微软官网下载SDK,默认装的就是这个系列。但很多早期教程、老面试题书里用的还是.NET Framework 4.x,或者.NET Core 3.1。这两套东西新建项目的模板结构差别挺大,尤其是新版控制台模板默认不生成Program.cs里的Main方法,而是用顶级语句,直接导致你抄老代码时一头雾水。
我给你的建议是:如果是纯学习基础语法,直接装最新的.NET 8 SDK,然后用Visual Studio Community(免费的)或VS Code加C# Dev Kit插件都行。我自己更推荐新手用VS Community,因为调试器、断点、变量监视窗口这些功能开箱即用,省得在配置上浪费精力。
1.2 环境变量没配好,命令怎么都执行不了
用VS Code的同学经常会遇到dotnet --version在终端里能跑,但重启电脑后就不认识了,这基本是环境变量Path没配置好的问题。Windows下的.NET SDK安装包正常装完会自动把路径写进系统Path,如果你从绿色版、便携版解压来的,就得手动加一条指向SDK根目录的路径。
验证环境是否OK,我建议在终端里依次跑这三条命令:
dotnet --version dotnet --list-sdks dotnet new console -o HelloWorld cd HelloWorld dotnet run如果最后dotnet run能输出Hello, World!,你的环境就彻底没问题了。这个步骤别急着跳过,我见过太多人代码写对了,结果卡在SDK缺失上,白白浪费一晚上。
2. 第一组练习:变量、类型转换与控制流
环境搞定之后,先别急着去写花哨的功能,把C#的语法地基打牢。这一组的题目看似基础,但每一道我都加了“实际项目里你为什么需要它”的说明,这样你才知道不是白练。
2.1 练习题:交换两个变量的值
题目本身很朴素:声明两个整型变量a = 5, b = 10,写代码把它们的值互换。
大多数人第一反应是引入第三个临时变量:
int a = 5; int b = 10; int temp = a; a = b; b = temp; Console.WriteLine($"a={a}, b={b}");这当然对,但我想让你同时知道C#里的更优雅写法——元组解构:
int a = 5; int b = 10; (a, b) = (b, a); Console.WriteLine($"a={a}, b={b}");这道题练的是什么?是“你在初级开发里最常见的一个动作:变量赋值”。很多初学者搞不清“赋值是拷贝值还是拷贝引用”,理解透这个基准题,后面学类、学对象、学参数传递时就不会懵。另外提一嘴,面试官如果问你“怎么在不使用第三个变量的情况下交换两个整数”,那是在考异或运算a ^= b; b ^= a; a ^= b;,知道有这么回事就行,实际项目里没人这么写。
2.2 练习题:把字符串转成数字别再用Parse裸奔
实际开发中,从文本框、配置文件、Excel、扫码枪拿到的数据几乎全是字符串,但你计算时又需要数字。比如上位机里从扫码枪读到的可能是一串数字字符串,你要把它转成int才能做校验。
C#里最常见的三种转换方式:
string text = "123"; // 方式一:Convert int num1 = Convert.ToInt32(text); // 方式二:Parse int num2 = int.Parse(text); // 方式三:TryParse bool success = int.TryParse(text, out int num3);我强烈建议你在练习时就养成用TryParse的习惯,而不是前两种。原因很简单:Convert和Parse在遇到"abc"、""、null时会抛异常,程序直接崩;而TryParse返回bool告诉你是否成功,不抛异常,顶多是你自己决定失败后怎么处理。这一点做上位机的人体会最深——设备传来的数据偶尔就是脏的,你如果不在解析阶段兜底,后面写再多功能都是白搭。
练习时可以自己写一段代码,分别传合法数字、空字符串、带字母的字符串进去,观察三种方式的行为差异。这道题的背后,是对“程序鲁棒性”的第一次感知。
2.3 练习题:用循环打印九九乘法表只算入门
你在网上能搜到各种打印九九乘法表的写法,外层循环i从1到9,内层循环j从1到i,拼字符串输出。这题本身不难,但你练完之后要能回答自己三个问题:
- 为什么内层循环的结束条件得是
j <= i而不是j <= 9; - 为什么用
Console.Write而不是Console.WriteLine; - 怎么控制对齐,让输出像一个三角形而不是歪歪扭扭。
更进阶一点的变体是把乘法表改成倒序输出、接龙式输出、用StringBuilder拼接成一段字符串再一次性输出。最后这个StringBuilder的点很关键——你将来写日志、拼报文、拼上位机协议帧时都会用到它,比反复string +=拼接性能好得多,也避免产生一堆无用的中间字符串对象。
3. 第二组练习:字符串、日期与集合——频率最高的三个操作区
刷完第一组,你已经有了基本的逻辑能力。接下来这组更贴近日常开发:字符串、日期、集合。搜索热词里“C#语言怎样截取字符串”出现频率很高,说明大家都在实际场景里卡住了。这一组题目练完,你的“抄作业”面会宽很多。
3.1 拆字符串:不只是Split那么简单
实际场景举例:你从配置文件里读了一行"张三,25,开发部",想拆出姓名、年龄、部门。用Split:
string line = "张三,25,开发部"; string[] parts = line.Split(','); string name = parts[0]; int age = int.Parse(parts[1]); string department = parts[2];但真实数据往往没这么乖,比如可能带空格、带引号、有多余逗号。这时候就要学会组合拳:
string line = " 张三, 25 , 开发部 "; string[] parts = line.Split(','); string name = parts[0].Trim(); int age = int.Parse(parts[1].Trim()); string department = parts[2].Trim();Trim去掉首尾空白,这个细节工程里极其常用。更进一步,如果你要截取一段字符串中的指定部分,比如"订单号:SO-20240612-001"里的"SO-20240612-001",就得配合IndexOf定位冒号的位置再用Substring截取。练习时建议自己封装一个方法:
static string ExtractAfter(string source, string delimiter) { int index = source.IndexOf(delimiter); if (index < 0) return string.Empty; return source.Substring(index + delimiter.Length).Trim(); }注意检查IndexOf返回值,为-1表示没找到,这时候直接截取就会崩。这种“找不到就返回空串”的防御式写法,反映的是工程意识,而不是单纯语法熟练度。
3.2 日期时间处理:格式化与计算差值
搜索词里“C#更新本地时间”其实指的是利用DateTime.Now获取当前时间,或者同步系统时间。日期时间这块,新手最容易搞混的是格式化字符串。
先记住几个最常用的:
DateTime now = DateTime.Now; Console.WriteLine(now.ToString("yyyy-MM-dd HH:mm:ss")); // 2025-01-08 14:30:00 Console.WriteLine(now.ToString("yyyyMMddHHmmss")); // 20250108143000 Console.WriteLine(now.ToString("yyyy年MM月dd日 dddd")); // 2025年01月08日 星期三yyyy是四位年份,MM是两位月份,HH是24小时制,hh是12小时制,mm是分钟,ss是秒。初学者最容易把月份和分钟都写成mm,导致输出完全不是自己想要的。
再练一个高频操作:计算两个日期之间相差多少天。比如计算设备上次保养距今几天:
DateTime lastMaintenance = new DateTime(2024, 12, 1); DateTime now = DateTime.Now; TimeSpan diff = now - lastMaintenance; Console.WriteLine($"已保养 {diff.TotalDays:F1} 天");TimeSpan就是两个时间点之间的差,TotalDays属性直接告诉你天数。这些属于“你可以靠查资料查到,但你自己不写一遍,用的时候还是会卡”的知识点。
3.3 集合实战:List和Dictionary
集合是C#里最常打交道的类型家族。热词里有“泛型”相关的提问,说明很多人传参时被List<T>、Dictionary<K,V>搞晕过。
练习场景:统计一段英文文章里每个单词出现的次数。这是Dictionary的经典用法。
string text = "hello world hello csharp world world"; string[] words = text.Split(' '); Dictionary<string, int> counts = new Dictionary<string, int>(); foreach (string word in words) { if (counts.ContainsKey(word)) counts[word]++; else counts[word] = 1; } foreach (var pair in counts) Console.WriteLine($"{pair.Key}: {pair.Value}");这里的关键理解点:Dictionary的键是唯一的,你不能往里面塞两个同样的键,所以写入前要判断是否存在。等你把这段写顺了,后面处理配置表、设备参数映射、JSON反序列化结果时会非常顺手。
List<T>的练习可以这样设计:做一个学生分数列表,求最高分、最低分、平均分、按分数排序、去重。先试着自己用循环实现,再去学Linq里的Max()、Min()、Average()、OrderByDescending(),你才会真正理解这些扩展方法帮我们省了多少事。
4. 第三组练习:用类和继承组织代码,顺便把单例看了
基础语法刷得差不多了,就该上面向对象。很多新手学OOP时觉得“抽象”“封装”这些词太虚,其实就是因为没在具体场景里用过。这一组我用一个贴近业务的例子串起来。
4.1 练习题:设计一个员工类
先别管什么高大上的设计模式,从最简单的开始:定义一个Employee类,包含工号、姓名、工资,提供构造函数、属性和一个计算年薪的方法。
public class Employee { public string Id { get; set; } public string Name { get; set; } public decimal MonthlySalary { get; set; } public Employee(string id, string name, decimal monthlySalary) { Id = id; Name = name; MonthlySalary = monthlySalary; } public decimal GetAnnualSalary() { return MonthlySalary * 12; } }写完之后问自己三个问题:
- 为什么字段要用属性
{ get; set; }而不是public字段?因为在属性里你可以加校验逻辑,比如工资不能为负数,将来改内部实现不影响外部调用。 - 构造函数的作用是什么?它保证了对象创建时就处于一个合法状态。热词里有人搜“C#单例模式”,单例的私有构造函数其实就是这个思路的延伸——不让外部随便new,由类自己控制实例数量。
- 如果有一个
Manager类,它应该继承Employee,还是组合一个Employee?这里不急着给标准答案,但你要开始思考“is-a”和“has-a”的区别。继承在C#里是单继承,一个类只能有一个父类,所以如果用继承,Manager可以额外加Bonus属性和重写后的GetAnnualSalary。
4.2 练习题:手写一个线程安全的单例
“单例模式”是面试题和实际项目中的常客。最简单的写法是懒汉式加锁,我建议你把下面这个版本背熟并且理解它每一行的作用:
public class ConfigManager { private static readonly object _lock = new object(); private static ConfigManager _instance; private ConfigManager() { } public static ConfigManager Instance { get { if (_instance == null) { lock (_lock) { if (_instance == null) { _instance = new ConfigManager(); } } } return _instance; } } }这串代码你面试时能默写出来,基本就过关了。但比默写更重要的是理解:为什么加锁还要再判一次空?因为两个线程可能同时通过了第一个if,如果不在锁内再查一次,就会new出两个实例。这个模式叫“双重检查锁定”,你在上位机里管理串口对象、日志对象、配置对象时都会用到。
4.3 练习题:struct和class到底该选谁
热词里有人搜“C#结构体”,这确实是个容易混的点。简单记忆法:class是引用类型,存在堆上,传递时传引用,两个变量可能指向同一个对象;struct是值类型,存在栈上(或作为字段内嵌),传递时完整拷贝一份。
什么时候用struct?表示一个小的、不可变的数据载体时,比如坐标点Point、颜色Color、一个协议报文头。什么时候别用?对象较大、需要继承多态、生命周期较长时。这道题不用写多复杂,试着定义一个坐标struct,再定义一个坐标class,分别放进List里修改其中一个元素的X值,你会看到行为完全不同——List里存struct时修改的是副本,存class时修改的是原对象。这个现象实测一遍,比背十遍理论都管用。
5. 第四组练习:文件读写和异常处理,让程序真正能交付
前面的代码都在内存里玩,程序一关数据就没了。但真正的业务系统,文件读写、日志记录是绕不过去的。热词里有“C#后台处理前端传过来的excel”“asp.net c# streamreader”等搜索,说明很多人入职后第一件事就是跟文件打交道。
5.1 练习题:读取CSV文件并计算平均值
CSV是最常见的文本数据格式,结构简单,每行用逗号分隔。练习目标:读取一个包含多行成绩的文本文件,每行是“姓名,分数”,统计所有人的平均分并输出。
string path = @"C:\temp\scores.csv"; string[] lines = File.ReadAllLines(path, Encoding.UTF8); int total = 0; int count = 0; foreach (string line in lines) { if (string.IsNullOrWhiteSpace(line)) continue; string[] parts = line.Split(','); if (parts.Length < 2) continue; if (int.TryParse(parts[1].Trim(), out int score)) { total += score; count++; } } double average = count == 0 ? 0 : (double)total / count; Console.WriteLine($"平均分: {average:F2}");这里有两个新手必踩的坑。第一个坑是编码:用Windows记事本另存为UTF-8时可能带BOM,用File.ReadAllLines默认也会处理,但如果你从某些老系统导出的是GB2312编码文件,读出来就是乱码。这时候要指定Encoding.GetEncoding("GB2312")或使用Encoding.Default,具体得看来源系统。
第二个坑是脏数据:文件里可能有空行、格式不完整的行、转不了数字的字符串。我上面的代码用TryParse加continue跳过异常行,这就是工业级代码的思路——数据不干净是常态,程序不能一遇到脏数据就崩。
5.2 练习题:写一个带日志记录的异常处理模块
实际开发中,程序抛异常不可怕,可怕的是异常信息没记录下来,出问题了不知道哪里崩的。热词里有人搜“C#方法”,本质就是想学会把逻辑封装成可复用的方法。
练习要求:写一个方法,负责把一个文件路径作为参数传入,读取文件内容,如果文件不存在就记录错误日志并返回空字符串。
static string ReadFileSafely(string path) { try { if (!File.Exists(path)) { Log($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 文件不存在: {path}"); return string.Empty; } return File.ReadAllText(path, Encoding.UTF8); } catch (UnauthorizedAccessException) { Log($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 无权限访问: {path}"); return string.Empty; } catch (Exception ex) { Log($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 读取失败: {ex.Message}"); return string.Empty; } } static void Log(string message) { File.AppendAllText(@"C:\temp\app.log", message + Environment.NewLine, Encoding.UTF8); }try-catch的关键点在于:能捕获具体异常就尽量捕获具体异常,最后一个catch (Exception ex)兜底。别用空的catch {}把异常吞掉,那样出了问题连日志都没有,排查起来等于大海捞针。File.AppendAllText这个API很实用,反复调用也不会覆盖之前的日志。
6. 综合练习:把上述内容串成一个成绩管理系统
前面练了那么多题,最后一步一定要把它们串起来。单独的知识点就像零散的积木,你可千万别只会搭个单块,得能拼成一个完整的小房子。
这道综合题也是网上常见的“简单OA系统”或“学生管理系统”类题目的小型化版本:
题目:做一个控制台成绩管理程序,支持以下功能:
- 启动时从
scores.txt文件读取学生成绩; - 显示菜单:查看所有学生成绩、添加学生成绩、删除学生成绩、保存并退出;
- 查看时按总分排序;
- 添加的学生记录要能写回文件;
- 整个程序不能因为用户输入错误而崩溃。
这个练习看着简单,但你实际动手写会发现它同时用到了:类与对象(学生类)、集合(List存储)、字符串处理(解析每行文本)、文件读写(加载和保存)、异常处理(文件不存在、输入不合法)、甚至一点设计思维(把显示菜单和执行逻辑分开)。
我的建议是:先自己写一版,不用管写得好不好,能跑就行。然后回头再审代码,问自己几个问题:
- 菜单循环写对了吗?退出时是
break还是return? - 添加学生时,成绩输入用
int.Parse还是int.TryParse? - 文件保存时,是用
File.WriteAllLines一次写入,还是每添加一条就AppendAllText一次? - 删除学生时,是按姓名匹配还是按序号匹配?删不到人时程序会怎样?
这四道追问,比题本身更有价值。如果你能把这四句话对应的代码都改得漂亮,说明你对C#基础的理解已经进入“能干活”的层面了。
最后再分享一个我自己的习惯:学编程别只“看题解题”,最好给自己准备一个小本子(或者电子笔记),每做错一道题、每踩一个坑,把报错信息、原因、解决办法记下来。C#的很多坑是有共性的,比如“序列化时循环引用”“DateTime默认值不是null而是0001年1月1日”“字符串比较用==还是Equals”,这些东西光靠脑子记不靠谱,写下来才能变成自己的武器库。等你刷完这套基础题,试着去找一个简单的上位机项目或者Web API接口练练手,那时你会发现自己比之前从容得多。
本文还有配套的精品资源,点击获取