Week 02 - 阶段检查
第二周检查 C# 基础进阶能力:集合选择、LINQ 查询、分组聚合、泛型、异常处理和 CSV 清洗。
本周学习地图
| 阶段 | 先掌握什么 | 最终能写什么 |
|---|---|---|
| 集合选择 | List、Dictionary、HashSet、Queue、Stack | 能按需求选集合 |
| 查询 | Where、Select、OrderBy | 能筛选、转换、排序数据 |
| 统计 | GroupBy、Sum、Average、Count | 能按分类统计商品 |
| 复用 | 泛型方法和泛型类 | 能写类型安全的通用逻辑 |
| 稳定性 | 异常处理、TryParse、错误列表 | 能处理错误输入和脏数据 |
| 综合 | CSV 读取、清洗、导出 | 能把外部文本变成干净数据 |
本周自测项目:商品 CSV 清洗器
从空项目开始写一个 CSV 清洗程序,至少包含:
- 读取商品 CSV 文本。
- 校验名称、价格、库存。
- 合法行转成
Product对象。 - 错误行记录行号和原因。
- 输出清洗后的商品列表、错误列表、分类统计。
过关标准:能解释为什么用 List<Product> 保存商品、为什么用 TryParse 处理价格和库存、为什么错误行不能直接丢掉。
上位机阶段验收:多设备采集记录分析器
本周目标: 用集合、LINQ、异常和 CSV 处理多台设备的采集记录。
必须亲手完成:
- 按设备编号保存多条记录
- 筛选报警并按设备分组统计
- 导入 CSV 时跳过坏行并报告原因
过关标准: 你能说清 List、Dictionary、LINQ 分组和 try/catch 各自解决什么问题。
不要只看答案。新建一个空项目重做一次,运行成功后再故意改坏一处并自己排错。
本周流程图
读取 CSV 文本
-> Split 拆字段
-> Trim 清理文本
-> TryParse 转价格和库存
-> 合法行进入 List<Product>
-> 错误行进入 errors
-> LINQ 筛选、排序、分组统计
-> 输出清洗结果和错误报告本周主线
第二周的主线是:
选择合适集合保存多条数据
-> 用 LINQ 筛选、排序、转换
-> 用 GroupBy 做分组统计
-> 用泛型复用类型安全逻辑
-> 用异常处理保护失败场景
-> 用 CSV 清洗把外部文本变成干净数据如果能把这条线写成一个能运行的小程序,第二周就算真正掌握。
必须掌握的能力
集合
- 能说清
List、Dictionary、HashSet、Queue、Stack的区别。 - 能按需求选择集合。
- 能用
TryGetValue安全读取字典。 - 能用
HashSet去重。 - 能用
Queue做先进先出。 - 能用
Stack做后进先出。
LINQ
- 能用
Where筛选。 - 能用
Select转换结果。 - 能用
OrderBy和OrderByDescending排序。 - 能用
Any判断是否存在。 - 能用
FirstOrDefault查找第一条,并判断null。 - 知道
ToList用来固定查询结果。
分组和聚合
- 能用
GroupBy按字段分组。 - 能解释
group.Key。 - 能使用
Count、Sum、Average、Max、Min。 - 能用匿名对象保存汇总结果。
泛型
- 能解释
T是类型占位符。 - 能使用
List<T>。 - 能写泛型方法
PrintItems<T>。 - 能写泛型类
Box<T>或Result<T>。 - 知道泛型比
object更类型安全。
异常和 CSV
- 输入错误优先用
TryParse。 - 文件读取失败用
try/catch。 catch里不能什么都不写。- CSV 每行要检查列数、空值、数字格式和业务范围。
- 有效数据和错误报告要分开保存。
第二周必须会用的 API 和方法
集合
| 类型 | 必须会的操作 |
|---|---|
List<T> | Add、Remove、Count、foreach、下标读取 |
Dictionary<TKey,TValue> | dict[key] = value、TryGetValue、ContainsKey、Remove、Keys、Values |
HashSet<T> | Add、Contains、Remove、Count |
Queue<T> | Enqueue、Peek、Dequeue、Count |
Stack<T> | Push、Peek、Pop、Count |
LINQ
| 需求 | 必须会的操作 |
|---|---|
| 筛选 | Where |
| 转换 | Select |
| 排序 | OrderBy、OrderByDescending、ThenBy |
| 判断 | Any、All |
| 查找 | FirstOrDefault |
| 固定结果 | ToList、ToArray |
| 统计 | Count、Sum、Average、Max、Min |
| 分组 | GroupBy、group.Key |
泛型、异常、CSV
| 主题 | 必须会的操作 |
|---|---|
| 泛型方法 | void PrintItems<T>(List<T> items) |
| 泛型类 | class Result<T>、class Box<T> |
| 泛型约束 | where T : new() |
| 输入校验 | int.TryParse、decimal.TryParse |
| 主动抛异常 | throw new ArgumentException(...) |
| 捕获异常 | try、catch、finally |
| 文件读取 | File.Exists、File.ReadAllLines |
| CSV 清洗 | Split(',')、Trim()、parts.Length、errors.Add(...) |
阶段检查题
题目 1:集合选择
写一个程序,要求:
- 用
List<string>保存商品名,允许重复。 - 用
HashSet<string>去重。 - 用
Dictionary<string, decimal>保存价格。 - 输出去重后的商品和价格。
题目 2:LINQ 查询
写一个商品查询程序,要求:
- 商品包含名称、价格、库存。
- 只保留库存大于 0 的商品。
- 只保留价格大于等于 50 的商品。
- 按价格从高到低排序。
- 输出商品名和价格。
题目 3:分组统计
写一个订单统计程序,要求:
- 订单包含分类、名称、金额。
- 按分类分组。
- 每组统计数量和总金额。
- 按总金额从高到低输出。
题目 4:泛型
写一个泛型方法 PrintItems<T>,要求:
- 可以打印
List<string>。 - 可以打印
List<int>。 - 方法体里用
foreach。
题目 5:异常处理
写一个安全读取文件程序,要求:
- 输入文件名。
- 文件名为空时提示。
- 文件不存在时提示。
- 读取成功时输出行数。
- 无论成功失败都输出收尾文字。
题目 6:综合 CSV 清洗
写一个综合程序,要求:
- 用字符串数组模拟 CSV。
- 第一行是表头。
- 每行包含分类、名称、金额。
- 清洗错误数据。
- 有效数据放进
List<Order>。 - 错误原因放进
List<string>。 - 按分类统计数量和总金额。
- 用泛型方法打印结果和错误。
完整答案
题目 1 答案
var productNames = new List<string> { "Keyboard", "Mouse", "Keyboard", "Cable" };
var uniqueNames = new HashSet<string>(productNames);
var prices = new Dictionary<string, decimal>
{
["Keyboard"] = 199m,
["Mouse"] = 59m,
["Cable"] = 19m
};
foreach (string name in uniqueNames)
{
if (prices.TryGetValue(name, out decimal price))
{
Console.WriteLine($"{name}: {price:F2}");
}
}题目 2 答案
var products = new List<Product>
{
new("Keyboard", 199m, 5),
new("Mouse", 59m, 0),
new("Monitor", 899m, 2),
new("Cable", 19m, 30)
};
var result = products
.Where(product => product.Stock > 0)
.Where(product => product.Price >= 50m)
.OrderByDescending(product => product.Price)
.Select(product => $"{product.Name}: {product.Price:F2}")
.ToList();
foreach (string line in result)
{
Console.WriteLine(line);
}
record Product(string Name, decimal Price, int Stock);题目 3 答案
var orders = new List<Order>
{
new("食品", "面包", 12m),
new("食品", "牛奶", 8m),
new("数码", "鼠标", 59m),
new("数码", "键盘", 199m)
};
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();
foreach (var item in summary)
{
Console.WriteLine($"{item.Category}: {item.Count} 条,{item.Total:F2}");
}
record Order(string Category, string Name, decimal Amount);题目 4 答案
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);
}
}题目 5 答案
Console.Write("请输入文件名: ");
string? fileName = Console.ReadLine();
try
{
if (string.IsNullOrWhiteSpace(fileName))
{
throw new ArgumentException("文件名不能为空");
}
string[] lines = File.ReadAllLines(fileName);
Console.WriteLine($"文件行数: {lines.Length}");
}
catch (ArgumentException ex)
{
Console.WriteLine(ex.Message);
}
catch (FileNotFoundException)
{
Console.WriteLine("文件不存在");
}
finally
{
Console.WriteLine("读取流程结束");
}题目 6 答案
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);
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);通过标准
第二周结束时,至少要能做到:
- 能根据需求选择集合。
- 能写出一条完整 LINQ 查询链。
- 能写
GroupBy分组统计。 - 能写一个简单泛型方法。
- 能用
try/catch保护文件读取。 - 能把 CSV 原始文本清洗成对象列表。
- 能把有效数据和错误数据分开处理。
如果没有通过
按这个顺序补:
- 回到 Day 1,重做集合选择。
- 回到 Day 2,重做 LINQ 查询。
- 回到 Day 3,重做
GroupBy统计。 - 回到 Day 4,重做
PrintItems<T>。 - 回到 Day 5,重做文件读取异常处理。
- 回到 Day 6,重做 CSV 清洗。
- 最后再做本页题目 6。
第二周通过后,第三周会进入面向对象基础:类、对象、继承、接口、委托和事件。