Skip to Content
Week 02📋 Week 02 阶段检查

Week 02 - 阶段检查

第二周检查 C# 基础进阶能力:集合选择、LINQ 查询、分组聚合、泛型、异常处理和 CSV 清洗。

本周学习地图

阶段先掌握什么最终能写什么
集合选择ListDictionaryHashSetQueueStack能按需求选集合
查询WhereSelectOrderBy能筛选、转换、排序数据
统计GroupBySumAverageCount能按分类统计商品
复用泛型方法和泛型类能写类型安全的通用逻辑
稳定性异常处理、TryParse、错误列表能处理错误输入和脏数据
综合CSV 读取、清洗、导出能把外部文本变成干净数据

本周自测项目:商品 CSV 清洗器

从空项目开始写一个 CSV 清洗程序,至少包含:

  • 读取商品 CSV 文本。
  • 校验名称、价格、库存。
  • 合法行转成 Product 对象。
  • 错误行记录行号和原因。
  • 输出清洗后的商品列表、错误列表、分类统计。

过关标准:能解释为什么用 List<Product> 保存商品、为什么用 TryParse 处理价格和库存、为什么错误行不能直接丢掉。

上位机阶段验收:多设备采集记录分析器

本周目标: 用集合、LINQ、异常和 CSV 处理多台设备的采集记录。

必须亲手完成:

  1. 按设备编号保存多条记录
  2. 筛选报警并按设备分组统计
  3. 导入 CSV 时跳过坏行并报告原因

过关标准: 你能说清 List、Dictionary、LINQ 分组和 try/catch 各自解决什么问题。

不要只看答案。新建一个空项目重做一次,运行成功后再故意改坏一处并自己排错。

本周流程图

读取 CSV 文本 -> Split 拆字段 -> Trim 清理文本 -> TryParse 转价格和库存 -> 合法行进入 List<Product> -> 错误行进入 errors -> LINQ 筛选、排序、分组统计 -> 输出清洗结果和错误报告

本周主线

第二周的主线是:

选择合适集合保存多条数据 -> 用 LINQ 筛选、排序、转换 -> 用 GroupBy 做分组统计 -> 用泛型复用类型安全逻辑 -> 用异常处理保护失败场景 -> 用 CSV 清洗把外部文本变成干净数据

如果能把这条线写成一个能运行的小程序,第二周就算真正掌握。

必须掌握的能力

集合

  • 能说清 ListDictionaryHashSetQueueStack 的区别。
  • 能按需求选择集合。
  • 能用 TryGetValue 安全读取字典。
  • 能用 HashSet 去重。
  • 能用 Queue 做先进先出。
  • 能用 Stack 做后进先出。

LINQ

  • 能用 Where 筛选。
  • 能用 Select 转换结果。
  • 能用 OrderByOrderByDescending 排序。
  • 能用 Any 判断是否存在。
  • 能用 FirstOrDefault 查找第一条,并判断 null
  • 知道 ToList 用来固定查询结果。

分组和聚合

  • 能用 GroupBy 按字段分组。
  • 能解释 group.Key
  • 能使用 CountSumAverageMaxMin
  • 能用匿名对象保存汇总结果。

泛型

  • 能解释 T 是类型占位符。
  • 能使用 List<T>
  • 能写泛型方法 PrintItems<T>
  • 能写泛型类 Box<T>Result<T>
  • 知道泛型比 object 更类型安全。

异常和 CSV

  • 输入错误优先用 TryParse
  • 文件读取失败用 try/catch
  • catch 里不能什么都不写。
  • CSV 每行要检查列数、空值、数字格式和业务范围。
  • 有效数据和错误报告要分开保存。

第二周必须会用的 API 和方法

集合

类型必须会的操作
List<T>AddRemoveCountforeach、下标读取
Dictionary<TKey,TValue>dict[key] = valueTryGetValueContainsKeyRemoveKeysValues
HashSet<T>AddContainsRemoveCount
Queue<T>EnqueuePeekDequeueCount
Stack<T>PushPeekPopCount

LINQ

需求必须会的操作
筛选Where
转换Select
排序OrderByOrderByDescendingThenBy
判断AnyAll
查找FirstOrDefault
固定结果ToListToArray
统计CountSumAverageMaxMin
分组GroupBygroup.Key

泛型、异常、CSV

主题必须会的操作
泛型方法void PrintItems<T>(List<T> items)
泛型类class Result<T>class Box<T>
泛型约束where T : new()
输入校验int.TryParsedecimal.TryParse
主动抛异常throw new ArgumentException(...)
捕获异常trycatchfinally
文件读取File.ExistsFile.ReadAllLines
CSV 清洗Split(',')Trim()parts.Lengtherrors.Add(...)

阶段检查题

题目 1:集合选择

写一个程序,要求:

  1. List<string> 保存商品名,允许重复。
  2. HashSet<string> 去重。
  3. Dictionary<string, decimal> 保存价格。
  4. 输出去重后的商品和价格。

题目 2:LINQ 查询

写一个商品查询程序,要求:

  1. 商品包含名称、价格、库存。
  2. 只保留库存大于 0 的商品。
  3. 只保留价格大于等于 50 的商品。
  4. 按价格从高到低排序。
  5. 输出商品名和价格。

题目 3:分组统计

写一个订单统计程序,要求:

  1. 订单包含分类、名称、金额。
  2. 按分类分组。
  3. 每组统计数量和总金额。
  4. 按总金额从高到低输出。

题目 4:泛型

写一个泛型方法 PrintItems<T>,要求:

  1. 可以打印 List<string>
  2. 可以打印 List<int>
  3. 方法体里用 foreach

题目 5:异常处理

写一个安全读取文件程序,要求:

  1. 输入文件名。
  2. 文件名为空时提示。
  3. 文件不存在时提示。
  4. 读取成功时输出行数。
  5. 无论成功失败都输出收尾文字。

题目 6:综合 CSV 清洗

写一个综合程序,要求:

  1. 用字符串数组模拟 CSV。
  2. 第一行是表头。
  3. 每行包含分类、名称、金额。
  4. 清洗错误数据。
  5. 有效数据放进 List<Order>
  6. 错误原因放进 List<string>
  7. 按分类统计数量和总金额。
  8. 用泛型方法打印结果和错误。

完整答案

题目 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 原始文本清洗成对象列表。
  • 能把有效数据和错误数据分开处理。

如果没有通过

按这个顺序补:

  1. 回到 Day 1,重做集合选择。
  2. 回到 Day 2,重做 LINQ 查询。
  3. 回到 Day 3,重做 GroupBy 统计。
  4. 回到 Day 4,重做 PrintItems<T>
  5. 回到 Day 5,重做文件读取异常处理。
  6. 回到 Day 6,重做 CSV 清洗。
  7. 最后再做本页题目 6。

第二周通过后,第三周会进入面向对象基础:类、对象、继承、接口、委托和事件。