C# 语言练习题(15 题)

学了语法得动手写才算真的会。这一页给 15 道精心挑选的练习题,按难度分三级——基础 6 题(变量 / 运算 / 控制流 / 方法)、进阶 5 题(LINQ / 异常处理)、实战 4 题(综合应用)。每题都含题目描述、提示、可折叠的参考答案,答案都是完整的、本地 dotnet run 一跑就能看到输出的程序。

使用建议:先别急着展开答案。读题后自己用本地 .NET 项目(或 dotnetfiddle 在线 C#)试着写,卡住了再看提示,实在写不出再对照参考答案。C# 的 LINQ 是它的杀手锏——能把 20 行的循环代码压成一行,做完进阶组你会深有体会。

基础(6 题):变量 / 运算 / 控制流 / 方法

这一组覆盖最基础的语言元素,目标是训练"现代 C#"的肌肉记忆——用 var 类型推导、string.Join 拼接、foreach 遍历,而不是 C 风格的 for 下标循环。

第 1 题:翻转字符串

题目:写一个翻转字符串的函数。比如 "hello" → "olleh"。要求给出两种写法:LINQ 版本与手写版本。

提示:LINQ 版 new string(s.Reverse().ToArray()) 一行搞定(需要 using System.Linq;);手写版把字符串转成 char[],用元组解包交换首尾 (arr[i], arr[j]) = (arr[j], arr[i])——C# 7+ 的元组交换,无需临时变量。

参考答案
using System;
using System.Linq;

// 方法 1:LINQ Reverse + ToArray(最简洁)
string Reverse1(string s) => new string(s.Reverse().ToArray());

// 方法 2:char 数组下标交换(理解原理)
string Reverse2(string s) {
    char[] arr = s.ToCharArray();
    for (int i = 0; i < arr.Length / 2; i++)
        (arr[i], arr[arr.Length - 1 - i]) = (arr[arr.Length - 1 - i], arr[i]);
    return new string(arr);
}

Console.WriteLine(Reverse1("hello"));     // olleh
Console.WriteLine(Reverse2("你好世界"));   // 界世好你

第 2 题:判断素数

题目:写一个函数 bool IsPrime(int n),判断正整数 n 是否为素数。

提示:小于 2 的都不是素数;只需检查从 2 到 sqrt(n) 的整除性——超过平方根的因子必然配对一个更小的、已经检查过的因子。用 (int)Math.Sqrt(n) 取平方根;返回类型用 bool(C# 原生支持)。

参考答案
using System;

bool IsPrime(int n) {
    if (n < 2) return false;
    for (int i = 2; i <= (int)Math.Sqrt(n); i++)
        if (n % i == 0) return false;
    return true;
}

Console.WriteLine(IsPrime(7));    // True
Console.WriteLine(IsPrime(10));   // False
Console.WriteLine(IsPrime(1));    // False
Console.WriteLine(IsPrime(2));    // True

第 3 题:FizzBuzz

题目:用 Console.WriteLine 打印 1 到 15。3 的倍数打印 Fizz,5 的倍数打印 Buzz,同时是 3 和 5 的倍数打印 FizzBuzz,其余打印数字本身。

提示:if / else if / else 的判断顺序很关键——必须先判"同时是 3 和 5 的倍数"(即 15 的倍数),否则会被前面的分支截胡。

参考答案
using System;

for (int i = 1; i <= 15; i++) {
    if (i % 15 == 0)        // 必须先判 15
        Console.WriteLine("FizzBuzz");
    else if (i % 3 == 0)
        Console.WriteLine("Fizz");
    else if (i % 5 == 0)
        Console.WriteLine("Buzz");
    else
        Console.WriteLine(i);
}
// 输出:
// 1
// 2
// Fizz
// 4
// Buzz
// Fizz
// 7
// 8
// Fizz
// Buzz
// 11
// Fizz
// 13
// 14
// FizzBuzz

第 4 题:数组最大值(不用 LINQ,遍历)

题目:不使用 LINQ 的 .Max(),写一个方法 int FindMax(int[] arr),返回数组中的最大值。

提示:这是"打擂台"算法——变量初始化为数组首元素,从下标 1 开始遍历,遇到更大的就更新。完成后可以对比一下 arr.Max()(LINQ 版)有多简洁。

参考答案
using System;

int FindMax(int[] arr) {
    int m = arr[0];
    for (int i = 1; i < arr.Length; i++)
        if (arr[i] > m) m = arr[i];
    return m;
}

int[] a = { 3, 1, 4, 1, 5, 9, 2, 6 };
Console.WriteLine(FindMax(a));    // 9
// 提示:实际项目可用 a.Max()(System.Linq)

第 5 题:数组求和

题目:写一个方法 int SumOf(int[] arr),返回数组所有元素之和。

提示:用 foreach (int x in arr) 累加;实际项目里 LINQ 的 arr.Sum() 一行搞定,但理解原理仍重要——LINQ 底层就是这么实现的。

参考答案
using System;

int SumOf(int[] arr) {
    int total = 0;
    foreach (int x in arr) total += x;
    return total;
}

int[] a = { 1, 2, 3, 4, 5 };
Console.WriteLine(SumOf(a));    // 15
// 提示:实际项目可用 a.Sum()(System.Linq)

第 6 题:计算阶乘

题目:写方法 long Factorial(int n),返回 n!。要求 Factorial(0) == 1。同时用循环和递归两种写法。

提示:int 装不下 13!,所以返回类型用 long;递归版可用表达式体方法 long FactorialRec(int n) => n <= 1 ? 1 : n * FactorialRec(n - 1);——C# 的方法定义比 Java 简洁得多。

参考答案
using System;

// 方法 1:循环
long Factorial(int n) {
    long r = 1;
    for (int i = 2; i <= n; i++) r *= i;
    return r;
}

// 方法 2:递归(用三元表达式,更紧凑)
long FactorialRec(int n) => n <= 1 ? 1 : n * FactorialRec(n - 1);

Console.WriteLine(Factorial(5));     // 120
Console.WriteLine(Factorial(0));     // 1
Console.WriteLine(FactorialRec(6));  // 720

进阶(5 题):LINQ / 异常处理

这一组是 C# 区别于 Java/C++ 的最大杀手锏——LINQ(Language Integrated Query)。同一套 Where / Select / GroupBy / OrderBy 算子既能查对象集合,也能查数据库(Entity Framework)、XML。学完进阶组你会感觉别的语言都"啰嗦"。

第 7 题:LINQ Where 筛选偶数

题目:给定 int[] nums,用 LINQ 筛出所有偶数并打印。

提示:加上 using System.Linq; 即可启用 LINQ 扩展方法;nums.Where(n => n % 2 == 0) 返回 IEnumerable<int>(延迟求值);string.Join(", ", evens) 把集合拼成字符串。

参考答案
using System;
using System.Linq;

int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8 };

// Where 是"过滤":保留满足条件的元素,返回 IEnumerable<int>
var evens = nums.Where(n => n % 2 == 0);

Console.WriteLine(string.Join(", ", evens));
// 输出: 2, 4, 6, 8
// 提示:lambda 表达式 n => n % 2 == 0 读作"n 真则 n 除以 2 余 0"。

第 8 题:LINQ Select 投影

题目:给定一个匿名类型的学生数组(Name、Score),用 LINQ 取出所有学生的 Name 字段。

提示:Select(s => s.Name) 把 Student 投影成 string——相当于 SQL 的 SELECT name FROM students;匿名类型 new { Name = "...", Score = 90 } 让你不用先定义类。

参考答案
using System;
using System.Linq;

var students = new[] {
    new { Name = "小明", Score = 90 },
    new { Name = "小红", Score = 85 },
    new { Name = "小刚", Score = 78 }
};

// Select 是"映射":每个 Student 投影成它的 Name 字段
var names = students.Select(s => s.Name);

Console.WriteLine(string.Join(", ", names));
// 输出: 小明, 小红, 小刚

第 9 题:LINQ GroupBy 分组

题目:给定一个员工列表(姓名 + 部门),按部门分组并打印每个部门的人员名单。

提示:GroupBy(u => u.Dept) 返回 IEnumerable<IGrouping<TKey, T>>;每个分组有 .Key(分组键)和它自己(可迭代);插值字符串 $"...{g.Key}: ..." 让格式化比 string.Format 直观。

参考答案
using System;
using System.Linq;

var users = new[] {
    new { Name = "小明", Dept = "技术部" },
    new { Name = "小红", Dept = "市场部" },
    new { Name = "小刚", Dept = "技术部" }
};

// GroupBy 返回 IEnumerable<IGrouping<TKey, T>>,每组有 Key 和组内元素
var groups = users.GroupBy(u => u.Dept);
foreach (var g in groups)
    Console.WriteLine($"{g.Key}: {string.Join(", ", g.Select(u => u.Name))}");
// 输出:
// 技术部: 小明, 小刚
// 市场部: 小红

第 10 题:LINQ OrderBy + Average 聚合

题目:给定一组分数,用 LINQ 升序排序并打印,再用 .Average() 算平均分。

提示:OrderBy(s => s) 升序、OrderByDescending 降序;.Average() 直接返回平均值(double);插值 $"平均: {avg:F1}" 中的 :F1 表示保留 1 位小数。其他聚合算子还有 Sum / Min / Max / Count。

参考答案
using System;
using System.Linq;

int[] scores = { 78, 95, 62, 88, 70 };

var sorted = scores.OrderBy(s => s);          // 升序(Descending 倒序)
double avg = scores.Average();                 // Average 直接算平均值

Console.WriteLine($"升序: {string.Join(", ", sorted)}");
Console.WriteLine($"平均: {avg:F1}");
// 输出:
// 升序: 62, 70, 78, 88, 95
// 平均: 78.6
// 提示:其他聚合算子 — Sum / Min / Max / Count。

第 11 题:try/catch 处理除零

题目:写一个 Divide(int a, int b) 方法,当 b == 0 时捕获 DivideByZeroException 并打印错误,而不是让程序崩溃。

提示:C# 中整数除以 0 会抛 DivideByZeroException(浮点除零则返回 Infinity);try { ... } catch (DivideByZeroException ex) { ... };ex.Message 是系统给的错误描述。

参考答案
using System;

Console.WriteLine(Divide(10, 2));    // 5
Console.WriteLine(Divide(10, 0));    // 捕获异常,打印错误并返回 0

int Divide(int a, int b) {
    try {
        return a / b;    // 整数除法,b==0 时抛 DivideByZeroException
    } catch (DivideByZeroException ex) {
        Console.WriteLine($"错误: {ex.Message}");
        return 0;
    }
}

实战(4 题):综合应用

这一组每题都会综合用到多个知识点——面向对象、LINQ、JSON、枚举与模式匹配——做出一个接近真实场景的小程序。建议每题都完整跑一遍,再想想怎么扩展。

第 12 题:学生管理(List<Student> + LINQ 排序/分组)

题目:定义 record Student(姓名 + 数学 + 语文 + 算均分的方法),用 List<Student> 存储;用 LINQ 按均分降序排名,再按"数学是否≥85"分组。

提示:用 record(C# 9+)替代 class,一行定义主构造 record Student(string Name, int Math, int Chinese);OrderByDescending(s => s.Average()) 排序;GroupBy(s => s.Math >= 85) 分组——key 是 bool。

参考答案
using System;
using System.Collections.Generic;
using System.Linq;

var students = new List<Student> {
    new("小明", 90, 80),
    new("小红", 95, 88),
    new("小刚", 70, 100)
};

// 1. 按均分降序排序
Console.WriteLine("按均分排名:");
foreach (var s in students.OrderByDescending(s => s.Average()))
    Console.WriteLine($"  {s.Name} 均分 {s.Average():F1}");

// 2. 按数学是否>=85分组
Console.WriteLine("数学>=85分组:");
foreach (var g in students.GroupBy(s => s.Math >= 85))
    Console.WriteLine($"  {g.Key}: {string.Join(",", g.Select(s => s.Name))}");

record Student(string Name, int Math, int Chinese) {
    public double Average() => (Math + Chinese) / 2.0;
}
// 输出:
// 按均分排名:
//   小红 均分 91.5
//   小明 均分 85.0
//   小刚 均分 85.0
// 数学>=85分组:
//   True: 小明,小红
//   False: 小刚

第 13 题:词频统计(LINQ GroupBy + OrderByDescending)

题目:统计一段英文文本中每个单词的出现次数,输出频次最高的 Top 3(频次相同按字典序)。

提示:用 LINQ 链式调用——Split(' ') 分词 → GroupBy(w => w) 分组 → Select(g => (g.Key, g.Count())) 投影 → OrderByDescending(x => x.Count) 主排序 → ThenBy(x => x.Word) 次排序 → Take(3) 取前 N。这是 LINQ 的招牌写法。

参考答案
using System;
using System.Linq;

string text = "the quick brown fox the lazy dog the fox brown dog dog";

var top = text.Split(' ')
    .GroupBy(w => w)                                  // 按单词分组
    .Select(g => (Word: g.Key, Count: g.Count()))     // 投影成 (单词, 次数)
    .OrderByDescending(x => x.Count)                  // 次数降序
    .ThenBy(x => x.Word)                              // 次数相同按字典序
    .Take(3);                                          // 只取前 3

foreach (var item in top)
    Console.WriteLine($"  {item.Word}: {item.Count}");
// 输出:
//   dog: 3
//   the: 3
//   brown: 2
// 提示:LINQ 链式调用把"过滤/映射/分组/排序/取前 N"压缩成 5 行,这是 C# 的招牌。

第 14 题:JSON 文件读写(System.Text.Json 序列化)

题目:定义一个 Person 类,用 System.Text.Json 把对象序列化成 JSON 写到文件,再从文件反序列化回来打印。

提示:JsonSerializer.Serialize(obj, opt) 序列化,Deserialize<T>(json) 反序列化;JsonSerializerOptions { WriteIndented = true } 让输出带缩进易读;类的属性必须同时有 get/set,否则反序列化无法写入。

参考答案
using System;
using System.IO;
using System.Text.Json;

var p = new Person { Name = "小明", Age = 28 };

// 序列化(WriteIndented = true 让输出带缩进,易读)
var opt = new JsonSerializerOptions { WriteIndented = true };
string json = JsonSerializer.Serialize(p, opt);
File.WriteAllText("person.json", json);

// 反序列化(string -> 对象)
Person? p2 = JsonSerializer.Deserialize<Person>(json);
Console.WriteLine($"反序列化: {p2?.Name}, {p2?.Age} 岁");

class Person {
    public string Name { get; set; } = "";   // set 必须有,反序列化才能写入
    public int Age { get; set; }
}
// 输出(屏幕):
// 反序列化: 小明, 28 岁
// 文件 person.json:
// {
//   "Name": "小明",
//   "Age": 28
// }

第 15 题:简易计算器(枚举 Op + switch 表达式)

题目:用 枚举 Op + switch 表达式实现一个简易计算器,支持 + - * /;除数为 0 返回 NaN;未知运算抛异常。

提示:C# 8+ 的 switch 表达式用 => 分支和 _(丢弃模式,等同 default);比传统 switch 语句更紧凑、有返回值;枚举让调用方不能传非法字符串——编译期就帮你查错。

参考答案
using System;

enum Op { Add, Sub, Mul, Div }

// C# 8+ switch 表达式:模式匹配,一行写完所有分支
double Calc(double a, Op op, double b) => op switch {
    Op.Add => a + b,
    Op.Sub => a - b,
    Op.Mul => a * b,
    Op.Div => b == 0 ? double.NaN : a / b,
    _ => throw new ArgumentException("未知运算")
};

Console.WriteLine(Calc(10, Op.Add, 3));   // 13
Console.WriteLine(Calc(10, Op.Sub, 3));   // 7
Console.WriteLine(Calc(10, Op.Mul, 3));   // 30
Console.WriteLine(Calc(10, Op.Div, 4));   // 2.5
Console.WriteLine(Calc(10, Op.Div, 0));   // NaN
// 提示:_ 是丢弃模式(类似 default);switch 表达式必须穷尽所有可能或加 _。

练习建议

不要只看答案。把每道题在本地或在线环境亲手敲一遍,改一改输入试试边界情况(空数组、负数、超长字符串、null)。C# 是强类型语言——编译器是你的朋友,多数错误在编译阶段就被揪出来了,看懂红色波浪线提示是 C# 程序员的硬通货。

试着扩展:做完第 12 题学生管理,能不能改成"按单科排名并打印 Top N"?做完第 13 题词频统计,能不能从真实文本文件读?(用 File.ReadAllText)做完第 15 题计算器,能不能解析字符串表达式(比如 "1+2*3")?每个扩展都是一次小型的真实开发。

更多练习资源:推荐 LeetCode 用 C# 刷算法(LINQ 在面试里是亮点);官方文档 Microsoft Learn 的 C# 教程有完整的中文指南和交互式练习。

← 上一篇 C# 实战项目

返回 C# 教程目录

✈️💬