Day 7 - 复盘与巩固
建议用时:150-180 分钟
你将学会什么
- 如何把第二周知识串成一条完整路线
- 如何判断该用哪种集合
- 如何把 CSV 文本清洗成对象列表
- 如何用 LINQ 做筛选、分组和统计
- 如何用泛型写通用输出方法
- 如何用异常处理保护文件读取
- 如何做一个第二周综合小练习
本页不是只写复盘文字。今天要把第二周的集合、LINQ、泛型、异常和 CSV 清洗合成一个能运行的小程序。
本页固定顺序
- 先学第一部分:弄懂今天最小、最重要的知识,并运行短例子。
- 再学第二部分:把刚学的知识组合成一个完整例子。
- 然后做第三部分:自己跟着敲,再完成重复训练和每日小测。
- 最后做第四部分:先独立完成作业,再用完整答案检查。
学习衔接
上一页学习的是“小项目: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.Key | group.Key 是当前组的分组值 |
泛型当成 object | 泛型是类型安全,不是乱放 |
空 catch | 至少输出或记录错误 |
| CSV 不检查列数 | 先判断 parts.Length |
第二周常用操作总表
| 需求 | 常用写法 | 用在哪一类问题 |
|---|---|---|
| 保留原始数据 | List<T> | CSV 清洗后的有效对象 |
| 去重 | HashSet<T> | 商品名、角色、标签 |
| 按 key 查找 | Dictionary<TKey,TValue> | 商品编号查价格 |
| 安全查字典 | TryGetValue | key 可能不存在 |
| 先进先出 | Queue<T>、Enqueue、Dequeue | 任务队列 |
| 后进先出 | Stack<T>、Push、Pop | 撤销操作 |
| 筛选 | Where | 只保留有效数据 |
| 转换 | Select | 对象转输出文本 |
| 排序 | OrderBy、OrderByDescending | 报表排序 |
| 是否存在 | Any | 有没有符合条件的数据 |
| 查第一条 | FirstOrDefault | 找一个商品或订单 |
| 分组 | GroupBy | 分类、客户、状态统计 |
| 求和 | Sum | 金额、库存、数量 |
| 平均 | Average | 平均金额 |
| 最高最低 | Max、Min | 最高价格、最低价格 |
| 泛型方法 | 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。
第四部分:作业完整答案
这一部分给出当天作业的完整答案。先按第三部分做一遍,再用这里检查。
作业要求
写一个第二周综合程序,要求:
- 用 CSV 行模拟订单数据。
- 每行有分类、名称、金额。
- 清洗错误数据。
- 有效数据放进
List<Order>。 - 错误原因放进
List<string>。 - 按分类统计订单数量和总金额。
- 用泛型方法打印汇总和错误。
- 加一个
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处理。