Skip to Content
Week 02Day 7 - 复盘与巩固

Day 7 - 复盘与巩固

建议用时:150-180 分钟

你将学会什么

  • 如何把第二周知识串成一条完整路线
  • 如何判断该用哪种集合
  • 如何把 CSV 文本清洗成对象列表
  • 如何用 LINQ 做筛选、分组和统计
  • 如何用泛型写通用输出方法
  • 如何用异常处理保护文件读取
  • 如何做一个第二周综合小练习

本页不是只写复盘文字。今天要把第二周的集合、LINQ、泛型、异常和 CSV 清洗合成一个能运行的小程序。

本页固定顺序

  1. 先学第一部分:弄懂今天最小、最重要的知识,并运行短例子。
  2. 再学第二部分:把刚学的知识组合成一个完整例子。
  3. 然后做第三部分:自己跟着敲,再完成重复训练和每日小测。
  4. 最后做第四部分:先独立完成作业,再用完整答案检查。

学习衔接

上一页学习的是“小项目:CSV 清洗”,今天继续学习“复盘与巩固”。先使用上一页已经会的写法,再只增加今天这个新知识点;如果前置内容还不能独立敲出,先回上一页复习,不要硬跳。

今天的最低通过线

第一次学习不要求背完整页。完成下面 3 项,就可以继续:

  • 能用自己的话说明“复盘与巩固”解决什么问题。
  • 把第一部分的短例子亲手敲完,并确认每个例子都能运行。
  • 不看完整答案完成第三部分至少前 3 个例子,再主动改一个值观察结果。

第一部分:先学原理和最小知识

这一部分先解决“这是什么、为什么要这样写”。每看到一个短例子就亲手敲一遍并运行,不要先背完整程序。

第二周主线

第二周不是孤立知识点,它们之间的关系是:

集合:保存多条数据 LINQ:查询、筛选、排序、转换集合 分组聚合:把多条数据汇总成报表 泛型:让集合和方法保持类型安全 异常处理:保护文件、输入、边界失败 CSV 清洗:把外部文本变成干净数据

如果写成程序,就是:

读取原始行 -> 清洗错误数据 -> 保存有效对象 -> LINQ 统计 -> 泛型方法输出结果 -> 异常处理保护失败

本周关键词复盘

关键词解决什么问题最小判断
List保存有顺序、可重复的数据要不要保留原始顺序
Dictionary按 key 快速查 value是否经常按编号查找
HashSet去重和存在性判断是否不允许重复
Queue先进先出是否像排队
Stack后进先出是否像撤销
Where筛选集合要不要保留某些元素
Select转换形状要不要变成另一种结果
GroupBy分组是否按分类、客户、状态汇总
泛型复用同一套类型安全逻辑是否只有类型不同
异常处理正常流程无法继续的失败文件、权限、越界
CSV 清洗外部文本转干净数据是否要逐行校验

集合选择复盘

选集合时不要先想名字,先问问题:

要保留顺序和重复吗?用 List。 要按编号查吗?用 Dictionary。 要自动去重吗?用 HashSet。 要按先来后到处理吗?用 Queue。 要撤销最近操作吗?用 Stack。

LINQ 复盘

LINQ 读法从左到右:

原始集合 -> Where 筛选 -> OrderBy 排序 -> Select 转换 -> ToList 固定结果

分组统计读法:

原始集合 -> GroupBy 分组 -> Select 生成汇总对象 -> Count/Sum/Average 做聚合

泛型复盘

泛型解决的是:

逻辑一样,只是类型不同

例如:

PrintItems(new List<string> { "Keyboard", "Mouse" }); PrintItems(new List<int> { 80, 95 }); void PrintItems<T>(List<T> items) { foreach (T item in items) { Console.WriteLine(item); } }

同一个 PrintItems<T> 可以打印不同类型的列表。

异常复盘

输入错误优先校验:

if (!decimal.TryParse("abc", out decimal amount)) { Console.WriteLine("金额格式错误"); }

系统错误用 try/catch

try { string[] lines = File.ReadAllLines("orders.csv"); Console.WriteLine(lines.Length); } catch (FileNotFoundException) { Console.WriteLine("文件不存在"); }

本周常见错误

错误修法
Dictionary 直接按不存在 key 读取TryGetValue
LINQ 写了 Where 以为原集合被修改记住 LINQ 返回新查询结果
Select 当成筛选筛选用 Where,转换用 Select
GroupBy 后不知道 group.Keygroup.Key 是当前组的分组值
泛型当成 object泛型是类型安全,不是乱放
catch至少输出或记录错误
CSV 不检查列数先判断 parts.Length

第二周常用操作总表

需求常用写法用在哪一类问题
保留原始数据List<T>CSV 清洗后的有效对象
去重HashSet<T>商品名、角色、标签
按 key 查找Dictionary<TKey,TValue>商品编号查价格
安全查字典TryGetValuekey 可能不存在
先进先出Queue<T>EnqueueDequeue任务队列
后进先出Stack<T>PushPop撤销操作
筛选Where只保留有效数据
转换Select对象转输出文本
排序OrderByOrderByDescending报表排序
是否存在Any有没有符合条件的数据
查第一条FirstOrDefault找一个商品或订单
分组GroupBy分类、客户、状态统计
求和Sum金额、库存、数量
平均Average平均金额
最高最低MaxMin最高价格、最低价格
泛型方法PrintItems<T>打印不同类型列表
泛型结果Result<T>成功/失败加数据
捕获文件错误try/catch文件读取失败
主动抛业务错误throw new ArgumentException(...)参数不合法
CSV 拆列Split(',')基础 CSV
字段清理Trim()去掉 CSV 字段空格
错误报告errors.Add(...)记录第几行错了

第二部分:把知识组合成完整例子

前面已经学过最小知识。现在把它们组合起来,先读懂执行顺序,再完整敲一遍。今天最终要写一个“从 CSV 行到分类统计”的综合程序。

最终你要写出这个程序

string[] rows = { "category,name,amount", "食品,面包,12", "数码,鼠标,59", "食品,牛奶,8", "数码,键盘,199", "办公,笔记本,abc" }; var orders = new List<Order>(); var errors = new List<string>(); for (int i = 1; i < rows.Length; i++) { string[] parts = rows[i].Split(','); if (parts.Length != 3) { errors.Add($"第 {i + 1} 行:列数错误"); continue; } string category = parts[0].Trim(); string name = parts[1].Trim(); string amountText = parts[2].Trim(); if (string.IsNullOrWhiteSpace(category) || string.IsNullOrWhiteSpace(name)) { errors.Add($"第 {i + 1} 行:分类或名称为空"); continue; } if (!decimal.TryParse(amountText, out decimal amount)) { errors.Add($"第 {i + 1} 行:金额格式错误"); continue; } orders.Add(new Order(category, name, amount)); } var summary = orders .GroupBy(order => order.Category) .Select(group => new { Category = group.Key, Count = group.Count(), Total = group.Sum(order => order.Amount) }) .OrderByDescending(item => item.Total) .ToList(); PrintItems(summary.Select(item => $"{item.Category}: {item.Count} 条,{item.Total:F2}").ToList()); PrintItems(errors); void PrintItems<T>(List<T> items) { foreach (T item in items) { Console.WriteLine(item); } } record Order(string Category, string Name, decimal Amount);

输出:

数码: 2 条,258.00 食品: 2 条,20.00 第 6 行:金额格式错误

这段代码同时用到了:List、CSV 拆分、TryParse、LINQ 分组统计、泛型方法。

第三部分:跟着敲代码

从这里开始动手。每个例子都可以直接放进 Program.cs 运行。

例子 1:集合选择小练习

var names = new List<string> { "Keyboard", "Mouse", "Keyboard" }; var uniqueNames = new HashSet<string>(names); var prices = new Dictionary<string, decimal> { ["Keyboard"] = 199m, ["Mouse"] = 59m }; Console.WriteLine($"原始数量: {names.Count}"); Console.WriteLine($"去重数量: {uniqueNames.Count}"); foreach (string name in uniqueNames) { if (prices.TryGetValue(name, out decimal price)) { Console.WriteLine($"{name}: {price:F2}"); } }

例子 2:LINQ 筛选和转换

var products = new List<Product> { new("Keyboard", 199m, 5), new("Mouse", 59m, 0), new("Cable", 19m, 20) }; var lines = products .Where(product => product.Stock > 0) .OrderBy(product => product.Price) .Select(product => $"{product.Name}: {product.Price:F2}") .ToList(); foreach (string line in lines) { Console.WriteLine(line); } record Product(string Name, decimal Price, int Stock);

例子 3:CSV 清洗到对象列表

var rows = new List<string> { "食品,面包,12", "数码,鼠标,59", "办公,笔记本,abc" }; var orders = new List<Order>(); var errors = new List<string>(); foreach (string row in rows) { string[] parts = row.Split(','); if (parts.Length != 3) { errors.Add($"列数错误: {row}"); continue; } if (!decimal.TryParse(parts[2], out decimal amount)) { errors.Add($"金额错误: {row}"); continue; } orders.Add(new Order(parts[0].Trim(), parts[1].Trim(), amount)); } Console.WriteLine($"有效订单: {orders.Count}"); Console.WriteLine($"错误数量: {errors.Count}"); record Order(string Category, string Name, decimal Amount);

例子 4:分组统计

var orders = new List<Order> { new("食品", "面包", 12m), new("数码", "鼠标", 59m), new("食品", "牛奶", 8m) }; var summary = orders .GroupBy(order => order.Category) .Select(group => new { Category = group.Key, Count = group.Count(), Total = group.Sum(order => order.Amount) }) .ToList(); foreach (var item in summary) { Console.WriteLine($"{item.Category}: {item.Count} 条,{item.Total:F2}"); } record Order(string Category, string Name, decimal Amount);

例子 5:泛型输出方法

PrintItems(new List<string> { "Keyboard", "Mouse" }); PrintItems(new List<int> { 80, 95 }); void PrintItems<T>(List<T> items) { foreach (T item in items) { Console.WriteLine(item); } }

小白重复敲写训练

复盘日不只是读总结。下面三个小程序要从空白 Program.cs 独立写出来。

训练 1:集合加 LINQ

var prices = new List<decimal> { 10m, 50m, 20m, 80m }; var result = prices.Where(price => price >= 30m).OrderBy(price => price); Console.WriteLine(string.Join(", ", result));

改动任务:改成筛选小于 30 的价格。

训练 2:字典安全查询

var stock = new Dictionary<string, int> { ["P001"] = 5, ["P002"] = 0 }; if (stock.TryGetValue("P001", out int count)) { Console.WriteLine(count); }

改动任务:查询 P999 并输出“编号不存在”。

训练 3:清洗一行数据并处理错误

string line = "Mouse,abc"; string[] parts = line.Split(','); if (parts.Length == 2 && decimal.TryParse(parts[1], out decimal price)) { Console.WriteLine($"{parts[0]}: {price:F2}"); } else { Console.WriteLine("数据格式错误"); }

改动任务:把 abc 改成正确价格,再独立重写一次。

每日小测

做完本页后,用这 5 题检查是否真的掌握。

1. 判断题

本页的目标不是只把代码运行起来,还要能说清楚“为什么这样写”。

答案:对。能运行只是第一步,能解释原理、常用操作和常见错误,才说明本页内容进入了可复用能力。

2. 填空题

本页主题是:复盘与巩固。今天至少要掌握的 3 个点是:

1. 如何把第二周知识串成一条完整路线 2. 如何判断该用哪种集合 3. 如何把 CSV 文本清洗成对象列表

答案:以上 3 点必须能用自己的代码跑通,不能只停留在阅读。

3. 流程题

遇到本页相关功能时,先按什么顺序处理?

答案:先看完整例子,确认最终效果;再读原理和名词;然后跟着第三部分从空项目敲代码;最后对照作业答案检查。

4. 找错误题

如果本页代码运行失败,第一步应该做什么?

答案:先看终端或 IDE 里的第一条错误,找到文件名和行号;不要同时改很多地方。再回到本页的“常见错误和修法”表格,对照错误类型逐项排查。

5. 改需求题

在本页完整例子跑通后,至少改一个小需求。

可选改法:

  • 改一个字段名称。
  • 多加一个校验条件。
  • 多输出一行结果。
  • 把固定数据改成用户输入。
  • 把一次处理改成多条数据处理。

答案标准:修改后能重新运行,并能说明这次修改影响了哪一段逻辑。重点检查:如何把第二周知识串成一条完整路线。

上位机专项练习

本周复盘要证明你能保存多台设备、筛选报警、分组统计并处理坏数据。

下面 3 个例子都要亲手敲。先运行原代码,再完成每个例子后面的改动任务。

专项例子 1:去重设备编号

var deviceIds = new HashSet<string> { "PLC-01", "PLC-01", "PLC-02" }; Console.WriteLine(string.Join(", ", deviceIds));

运行结果或界面效果:

PLC-01, PLC-02

改动任务: 再重复添加 PLC-02。

专项例子 2:筛选并排序报警值

var values = new List<double> { 72.5, 91.0, 81.2, 70.0 }; List<double> alarms = values .Where(value => value >= 80) .OrderByDescending(value => value) .ToList(); Console.WriteLine(string.Join(", ", alarms));

运行结果或界面效果:

91, 81.2

改动任务: 把报警线改成 75。

专项例子 3:安全读取设备状态

var statuses = new Dictionary<string, string> { ["PLC-01"] = "在线" }; if (statuses.TryGetValue("PLC-02", out string? status)) { Console.WriteLine(status); } else { Console.WriteLine("设备不存在"); }

运行结果或界面效果:

设备不存在

改动任务: 把查询编号改成 PLC-01。

第四部分:作业完整答案

这一部分给出当天作业的完整答案。先按第三部分做一遍,再用这里检查。

作业要求

写一个第二周综合程序,要求:

  1. 用 CSV 行模拟订单数据。
  2. 每行有分类、名称、金额。
  3. 清洗错误数据。
  4. 有效数据放进 List<Order>
  5. 错误原因放进 List<string>
  6. 按分类统计订单数量和总金额。
  7. 用泛型方法打印汇总和错误。
  8. 加一个 try/catch 版本,演示文件不存在时的提示。

完整代码

string[] rows = { "category,name,amount", "食品,面包,12", "数码,鼠标,59", "食品,牛奶,8", "数码,键盘,199", "办公,笔记本,abc", "办公,笔,5" }; var orders = new List<Order>(); var errors = new List<string>(); for (int i = 1; i < rows.Length; i++) { string row = rows[i]; int lineNumber = i + 1; string[] parts = row.Split(','); if (parts.Length != 3) { errors.Add($"第 {lineNumber} 行:列数错误"); continue; } string category = parts[0].Trim(); string name = parts[1].Trim(); string amountText = parts[2].Trim(); if (string.IsNullOrWhiteSpace(category) || string.IsNullOrWhiteSpace(name)) { errors.Add($"第 {lineNumber} 行:分类或名称为空"); continue; } if (!decimal.TryParse(amountText, out decimal amount)) { errors.Add($"第 {lineNumber} 行:金额格式错误"); continue; } if (amount <= 0) { errors.Add($"第 {lineNumber} 行:金额必须大于 0"); continue; } orders.Add(new Order(category, name, amount)); } var summary = orders .GroupBy(order => order.Category) .Select(group => new { Category = group.Key, Count = group.Count(), Total = group.Sum(order => order.Amount) }) .OrderByDescending(item => item.Total) .Select(item => $"{item.Category}: {item.Count} 条,{item.Total:F2}") .ToList(); Console.WriteLine("=== 分类统计 ==="); PrintItems(summary); Console.WriteLine("=== 错误报告 ==="); PrintItems(errors); try { string[] fileRows = File.ReadAllLines("missing-orders.csv"); Console.WriteLine($"读取文件行数: {fileRows.Length}"); } catch (FileNotFoundException) { Console.WriteLine("文件不存在"); } void PrintItems<T>(List<T> items) { if (items.Count == 0) { Console.WriteLine("无数据"); return; } foreach (T item in items) { Console.WriteLine(item); } } record Order(string Category, string Name, decimal Amount);

正确运行结果

你应该能看到:

=== 分类统计 === 数码: 2 条,258.00 食品: 2 条,20.00 办公: 1 条,5.00 === 错误报告 === 第 6 行:金额格式错误 文件不存在

你要能说清楚这 8 件事

  • 原始 CSV 行是字符串,不是对象。
  • 清洗后才进入 List<Order>
  • 错误行要记录原因。
  • GroupBy 用来按分类分组。
  • Sum 用来统计每组金额。
  • Select 用来把汇总结果变成可打印文本。
  • 泛型方法 PrintItems<T> 可以打印不同类型列表。
  • 文件读取失败用 try/catch 处理。