Day 4 - 委托与 Lambda
建议用时:220-260 分钟
你将学会什么
- 委托为什么存在,它解决什么问题
Func、Action、Predicate分别什么时候用- Lambda 的完整读法,不再只会照着写
=> - 如何把“判断规则”“计算规则”“输出动作”当参数传进去
- 闭包是什么,为什么 Lambda 能记住外面的变量
- 委托和 Lambda 在 LINQ、事件、回调里的真实用法
本页不要把 Lambda 当成神秘语法。它本质上就是“临时写一个小方法”,然后把这个小方法交给别人使用。
本页固定顺序
- 先学第一部分:弄懂今天最小、最重要的知识,并运行短例子。
- 再学第二部分:把刚学的知识组合成一个完整例子。
- 然后做第三部分:自己跟着敲,再完成重复训练和每日小测。
- 最后做第四部分:先独立完成作业,再用完整答案检查。
学习衔接
上一页学习的是“接口与抽象类”,今天继续学习“委托与 Lambda”。先使用上一页已经会的写法,再只增加今天这个新知识点;如果前置内容还不能独立敲出,先回上一页复习,不要硬跳。
今天的最低通过线
第一次学习不要求背完整页。完成下面 3 项,就可以继续:
- 能用自己的话说明“委托与 Lambda”解决什么问题。
- 把第一部分的短例子亲手敲完,并确认每个例子都能运行。
- 不看完整答案完成第三部分至少前 3 个例子,再主动改一个值观察结果。
第一部分:先学原理和最小知识
这一部分只读,不敲代码。先把名词弄清楚,第三部分再完整敲代码。
1. 委托是什么
普通变量保存的是数据:
int age = 18
string name = "Tom"
decimal price = 99.5委托变量保存的是一段“可以被调用的逻辑”。
你可以这样理解:
| 保存什么 | 示例 | 说明 |
|---|---|---|
| 保存数字 | int count | 以后可以拿这个数字计算 |
| 保存文字 | string name | 以后可以拿这个文字显示 |
| 保存方法 | Func<int, bool> rule | 以后可以调用这个规则 |
委托最核心的一句话:
委托 = 用变量保存方法2. 为什么要把方法保存起来
因为有些程序流程是固定的,但其中某一小段规则经常变化。
例如“筛选商品”这个流程固定:
- 准备一批商品。
- 一个一个检查。
- 满足条件就输出。
变化的是“满足什么条件”:
| 场景 | 变化的规则 |
|---|---|
| 看有库存商品 | Stock > 0 |
| 看高价商品 | Price > 200 |
| 看库存充足商品 | Stock >= 5 |
| 看便宜商品 | Price <= 100 |
如果每个规则都写一个完整方法,重复内容会越来越多。更好的做法是:流程只写一次,规则当参数传进去。
3. 方法签名是什么
委托不是随便保存任何方法,它只保存“形状匹配”的方法。
方法的形状一般看两件事:
| 要看什么 | 例子 |
|---|---|
| 参数 | 这个方法需要几个输入,输入是什么类型 |
| 返回值 | 这个方法最后返回什么类型 |
例如:
输入 int,返回 bool可以对应:
Func<int, bool>读法是:
Func<输入类型, 返回类型>所以:
| 委托类型 | 读法 |
|---|---|
Func<int, bool> | 输入一个 int,返回一个 bool |
Func<decimal, decimal> | 输入一个 decimal,返回一个 decimal |
Func<string, int> | 输入一个 string,返回一个 int |
Func<int, int, int> | 输入两个 int,返回一个 int |
注意最后一个类型永远是返回值。
4. Func、Action、Predicate 的区别
这三个名字经常一起出现:
| 类型 | 是否有返回值 | 适合做什么 |
|---|---|---|
Func<T, TResult> | 有返回值 | 判断、计算、转换 |
Action<T> | 没有返回值 | 打印、记录日志、发送通知 |
Predicate<T> | 返回 bool | 专门表示判断条件 |
最常用的是 Func 和 Action。
判断一个商品是否有库存:
Func<Product, bool>把商品转换成显示文字:
Func<Product, string>打印一行日志,不需要返回结果:
Action<string>5. Lambda 是什么
Lambda 是一种写小方法的简短方式。
普通方法长这样:
bool IsPass(int score)
{
return score >= 60;
}Lambda 可以写成:
score => score >= 60读法:
给我一个 score,返回 score >= 60 的结果所以 => 不要读成“箭头”,而要读成:
左边是输入,右边是处理结果6. Lambda 的三种常见形状
只有一个参数时:
score => score >= 60多个参数时:
(price, count) => price * count有多行逻辑时:
score =>
{
bool passed = score >= 60;
return passed ? "通过" : "未通过";
}多行 Lambda 必须写 {},并且有返回值时必须写 return。
委托和 Lambda 常用写法速查
| 需求 | 写法 | 说明 |
|---|---|---|
| 有输入有返回 | Func<T, TResult> | 转换、计算 |
| 两个输入一个返回 | Func<T1, T2, TResult> | 单价乘数量 |
| 有输入无返回 | Action<T> | 打印、回调通知 |
| 判断条件 | Predicate<T> | 返回 bool |
| LINQ 筛选 | .Where(x => 条件) | Lambda 做规则 |
| LINQ 转换 | .Select(x => 结果) | Lambda 做映射 |
| 传方法 | Run(CheckPrice) | 不加括号 |
| 执行方法 | CheckPrice(100m) | 加括号 |
常用模板:
Func<decimal, int, decimal> calculateTotal = (price, count) => price * count;
Action<string> log = message => Console.WriteLine(message);
Predicate<decimal> isValidPrice = price => price > 0;
Console.WriteLine(calculateTotal(199m, 2));
log("保存成功");
Console.WriteLine(isValidPrice(10m));读 Lambda 时,固定按这句话:
左边是输入,右边是处理后得到的结果。7. “传方法”和“执行方法”不是一回事
这是很容易写错的地方。
假设有一个方法:
bool IsPass(int score)传方法给别人:
IsPass执行方法得到结果:
IsPass(80)区别是:
| 写法 | 含义 |
|---|---|
IsPass | 把这个方法本身交出去 |
IsPass(80) | 现在立刻执行,得到 true 或 false |
Where 要的是规则,不是规则执行后的某一个结果,所以要传方法本身或 Lambda。
8. 回调是什么
回调就是:我把一段逻辑交给你,你在合适的时候再调用它。
例子:
| 场景 | 谁保存回调 | 什么时候调用 |
|---|---|---|
| 按钮点击 | 按钮 | 用户点击时 |
| 过滤数据 | Where | 遍历每个元素时 |
| 保存成功后通知 | 保存方法 | 保存完成后 |
| 读取文件每一行 | 读取方法 | 读到每一行时 |
回调不神秘,就是“稍后调用的方法”。
9. 闭包是什么
Lambda 可以使用外面的变量,这叫闭包。
例如:
decimal minPrice = 100m;
product => product.Price >= minPrice这个 Lambda 不但使用了 product,还使用了外面的 minPrice。
这说明 Lambda 不是只能看自己参数,它也能记住外层作用域里的变量。
闭包很方便,但要记住:
Lambda 看到的是变量,不是变量当时的一张照片如果外面的变量后来被改了,Lambda 使用时看到的也可能是新值。
10. 本页要形成的判断标准
以后看到一段重复流程,可以这样判断是否需要委托:
| 问题 | 如果答案是“是” |
|---|---|
| 流程是不是固定的? | 固定流程可以写成一个方法 |
| 中间某个规则是不是经常变? | 变化规则可以用委托传入 |
| 这个规则是不是很短? | 可以用 Lambda |
| 这个规则是不是很长? | 写成普通方法再传进去 |
第二部分:把知识组合成完整例子
这一段先看效果,不要求马上理解每一行。今天学完以后,要能写出这种代码:把不同规则传给同一个处理流程。
先看效果:商品筛选器
List<Product> products = new List<Product>
{
new Product { Name = "键盘", Price = 199m, Stock = 8 },
new Product { Name = "鼠标", Price = 89m, Stock = 0 },
new Product { Name = "显示器", Price = 1299m, Stock = 3 },
new Product { Name = "耳机", Price = 299m, Stock = 12 }
};
void PrintProducts(string title, List<Product> source, Func<Product, bool> rule)
{
Console.WriteLine(title);
foreach (Product product in source.Where(rule))
{
Console.WriteLine($"{product.Name} | 价格: {product.Price} | 库存: {product.Stock}");
}
Console.WriteLine();
}
PrintProducts("有库存的商品", products, product => product.Stock > 0);
PrintProducts("价格超过 200 的商品", products, product => product.Price > 200m);
PrintProducts("库存充足并且价格不超过 300 的商品", products, product => product.Stock >= 5 && product.Price <= 300m);
class Product
{
public string Name { get; set; } = "";
public decimal Price { get; set; }
public int Stock { get; set; }
}这个程序里最重要的不是商品,而是这一句:
Func<Product, bool> rule它表示:PrintProducts 不固定筛选条件,而是把筛选条件交给调用者决定。
如果今天不学委托,这个程序通常会写成三个方法:
PrintProductsInStock
PrintProductsExpensive
PrintProductsEnoughStockAndCheap方法越来越多,重复代码也越来越多。委托解决的就是这个问题:固定流程只写一次,可变规则从外面传进来。
第三部分:跟着敲代码
从这里开始动手。每个例子都是完整代码,可以直接放进 Program.cs 运行。
动手前先做这 3 件事
- 打开一个控制台项目。
- 每次只保留一个例子的代码,运行通过后再换下一个。
- 运行后改一个数字或一段文字,再运行一次,看输出是否变化。
例子 1:先不用 Lambda,只用普通方法
List<int> scores = new List<int> { 45, 60, 88, 92 };
foreach (int score in scores)
{
bool IsPass(int score)
{
return score >= 60;
}
if (IsPass(score))
{
Console.WriteLine($"通过成绩: {score}");
}
}
这个版本没有委托,只是普通方法调用。先确认你能看懂:循环每个成绩,然后用 IsPass 判断。
例子 2:把普通方法保存到委托变量里
List<int> scores = new List<int> { 45, 60, 88, 92 };
Func<int, bool> rule = IsPass;
foreach (int score in scores)
{
if (rule(score))
{
Console.WriteLine($"通过成绩: {score}");
}
}
bool IsPass(int score)
{
return score >= 60;
}这里最关键的是:
Func<int, bool> rule = IsPass;rule 现在保存了 IsPass 这段判断逻辑。后面调用 rule(score),效果和调用 IsPass(score) 一样。
例子 3:把普通方法改成 Lambda
List<int> scores = new List<int> { 45, 60, 88, 92 };
Func<int, bool> rule = score => score >= 60;
foreach (int score in scores)
{
if (rule(score))
{
Console.WriteLine($"通过成绩: {score}");
}
}这一句:
score => score >= 60等价于一个很短的方法:
输入 score,返回 score 是否大于等于 60例子 4:把规则传给一个方法
List<int> scores = new List<int> { 45, 60, 88, 92 };
void PrintMatchedScores(string title, List<int> source, Func<int, bool> rule)
{
Console.WriteLine(title);
foreach (int score in source)
{
if (rule(score))
{
Console.WriteLine(score);
}
}
Console.WriteLine();
}
PrintMatchedScores("及格成绩", scores, score => score >= 60);
PrintMatchedScores("优秀成绩", scores, score => score >= 90);
PrintMatchedScores("不及格成绩", scores, score => score < 60);
这就是委托真正有用的地方:PrintMatchedScores 只写一次,但可以接收不同规则。
例子 5:Func 有返回值
Func<int, int> doubleValue = number => number * 2;
Func<int, int> squareValue = number => number * number;
int value = 6;
Console.WriteLine($"原始数字: {value}");
Console.WriteLine($"乘以 2: {doubleValue(value)}");
Console.WriteLine($"平方: {squareValue(value)}");Func<int, int> 的意思是:
输入 int,返回 int例子 6:Func 可以有多个输入
Func<decimal, int, decimal> calculateTotal = (price, count) => price * count;
decimal total1 = calculateTotal(19.9m, 3);
decimal total2 = calculateTotal(88m, 2);
Console.WriteLine($"第一笔合计: {total1}");
Console.WriteLine($"第二笔合计: {total2}");Func<decimal, int, decimal> 的读法是:
输入 decimal 和 int,返回 decimal最后一个类型是返回值。
例子 7:Action 没有返回值
Action<string> log = message =>
{
string time = DateTime.Now.ToString("HH:mm:ss");
Console.WriteLine($"[{time}] {message}");
};
log("开始保存订单");
log("订单保存成功");Action<string> 的意思是:
输入 string,不返回任何结果适合做打印、记录日志、发送通知这类动作。
例子 8:Predicate 专门表示判断条件
Predicate<int> isEven = number => number % 2 == 0;
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
foreach (int number in numbers)
{
if (isEven(number))
{
Console.WriteLine($"偶数: {number}");
}
}Predicate<int> 基本等价于:
Func<int, bool>只不过它的名字更强调“这是一个判断条件”。
例子 9:把 Lambda 用到 LINQ 的 Where
List<string> names = new List<string> { "Tom", "Jerry", "Alice", "Jack" };
List<string> longNames = names
.Where(name => name.Length >= 5)
.ToList();
foreach (string name in longNames)
{
Console.WriteLine(name);
}Where 要的是一个判断规则:
输入一个元素,返回 true 或 false返回 true 的留下,返回 false 的过滤掉。
例子 10:把 Lambda 用到 LINQ 的 Select
List<decimal> prices = new List<decimal> { 10m, 20m, 35m };
List<string> labels = prices
.Select(price => $"价格: {price} 元")
.ToList();
foreach (string label in labels)
{
Console.WriteLine(label);
}Select 要的是一个转换规则:
输入一个元素,返回转换后的新结果例子 11:多行 Lambda
List<int> scores = new List<int> { 45, 60, 88, 96 };
List<string> reports = scores
.Select(score =>
{
if (score >= 90)
{
return $"优秀: {score}";
}
if (score >= 60)
{
return $"通过: {score}";
}
return $"未通过: {score}";
})
.ToList();
foreach (string report in reports)
{
Console.WriteLine(report);
}多行 Lambda 适合逻辑稍微多一点的情况。如果继续变复杂,就应该改成普通方法。
例子 12:Lambda 使用外部变量
decimal minPrice = 100m;
List<decimal> prices = new List<decimal> { 59m, 120m, 300m, 80m };
List<decimal> matched = prices
.Where(price => price >= minPrice)
.ToList();
foreach (decimal price in matched)
{
Console.WriteLine($"满足最低价格: {price}");
}这里的 Lambda 用到了外面的 minPrice。这就是闭包的常见用法。
例子 13:闭包看到的是变量的新值
int limit = 10;
Func<int, bool> greaterThanLimit = number => number > limit;
Console.WriteLine(greaterThanLimit(12));
limit = 20;
Console.WriteLine(greaterThanLimit(12));运行结果会先输出 True,再输出 False。
原因是 Lambda 记住的是变量 limit,不是 limit 当时的值。
例子 14:传方法,不是执行方法
List<int> scores = new List<int> { 45, 60, 88, 92 };
List<int> passed = scores
.Where(IsPass)
.ToList();
foreach (int score in passed)
{
Console.WriteLine(score);
}
bool IsPass(int score)
{
return score >= 60;
}这里传的是:
IsPass不是:
IsPass(80)Where 会自己把每个成绩传给 IsPass。
例子 15:自己写一个 Where 的简化版
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
List<int> Filter(List<int> source, Func<int, bool> rule)
{
List<int> matched = new List<int>();
foreach (int item in source)
{
if (rule(item))
{
matched.Add(item);
}
}
return matched;
}
List<int> result = Filter(numbers, number => number % 2 == 0);
foreach (int number in result)
{
Console.WriteLine(number);
}
这个例子能帮你理解 Where 的思想:
- 它收到一批数据。
- 它收到一个判断规则。
- 它逐个检查。
- 符合规则的留下。
例子 16:自己写一个 Select 的简化版
List<int> numbers = new List<int> { 1, 2, 3 };
List<string> Map(List<int> source, Func<int, string> converter)
{
List<string> result = new List<string>();
foreach (int item in source)
{
string text = converter(item);
result.Add(text);
}
return result;
}
List<string> result = Map(numbers, number => $"数字是 {number}");
foreach (string text in result)
{
Console.WriteLine(text);
}
converter 的意思是转换器。它负责把 int 转成 string。
例子 17:回调动作
void SaveOrder(string orderNo, Action<string> afterSaved)
{
Console.WriteLine($"正在保存订单: {orderNo}");
Console.WriteLine("保存完成");
afterSaved($"订单 {orderNo} 已保存");
}
SaveOrder("A001", message => Console.WriteLine($"控制台日志: {message}"));
afterSaved 就是回调。保存方法不关心外面要怎么通知,只负责在保存完成后调用它。
例子 18:回调可以换成不同动作
void SaveOrder(string orderNo, Action<string> notify)
{
Console.WriteLine($"保存订单: {orderNo}");
notify($"订单 {orderNo} 保存成功");
Console.WriteLine();
}
SaveOrder("A001", message => Console.WriteLine($"普通提示: {message}"));
SaveOrder("A002", message => Console.WriteLine($"重要提醒: {message.ToUpper()}"));
同一个 SaveOrder 方法,传入不同 notify,最终表现就不同。
例子 19:商品规则完整练习
List<Product> products = new List<Product>
{
new Product { Name = "键盘", Price = 199m, Stock = 8 },
new Product { Name = "鼠标", Price = 89m, Stock = 0 },
new Product { Name = "显示器", Price = 1299m, Stock = 3 },
new Product { Name = "耳机", Price = 299m, Stock = 12 }
};
void Print(string title, List<Product> source, Func<Product, bool> rule)
{
Console.WriteLine(title);
foreach (Product product in source)
{
if (rule(product))
{
Console.WriteLine($"{product.Name} | {product.Price} | {product.Stock}");
}
}
Console.WriteLine();
}
Print("有库存", products, product => product.Stock > 0);
Print("价格低于 300", products, product => product.Price < 300m);
Print("有库存并且价格低于 300", products, product => product.Stock > 0 && product.Price < 300m);
class Product
{
public string Name { get; set; } = "";
public decimal Price { get; set; }
public int Stock { get; set; }
}到这里,你已经把今天的核心都用上了:委托变量、Lambda、规则传参、对象筛选。
常见错误和修法
| 错误 | 为什么错 | 修法 |
|---|---|---|
| Lambda 写太长 | 可读性差,难调试 | 超过几行就提取成方法 |
| 委托参数顺序记不清 | 调用时容易传错 | 给参数起清楚名字,先看委托定义 |
| 捕获外部变量后被修改 | Lambda 使用的是变量,不是当时的值 | 循环里先复制到局部变量 |
Func 和 Action 混淆 | 一个有返回值,一个没有 | 有返回值用 Func,只做动作用 Action |
| 过度使用委托 | 简单逻辑变复杂 | 只有需要传递行为时再用 |
小白重复敲写训练
委托和 Lambda 必须从“把方法放进变量”开始练。
训练 1:Action 保存一个动作
Action sayHello = () => Console.WriteLine("Hello");
sayHello();
sayHello();改动任务:让它接收一个名字,类型改成 Action<string>。
训练 2:Func 保存一个计算
Func<int, int, int> add = (a, b) => a + b;
int result = add(2, 3);
Console.WriteLine(result);第二遍写一个 multiply,返回两个数的乘积。
训练 3:Lambda 配合集合筛选
var numbers = new List<int> { 1, 2, 3, 4, 5 };
Func<int, bool> isEven = number => number % 2 == 0;
foreach (int number in numbers.Where(isEven))
{
Console.WriteLine(number);
}第三遍改成筛选大于 3 的数字。
每日小测
做完本页后,用这 5 题检查是否真的掌握。
1. 判断题
本页的目标不是只把代码运行起来,还要能说清楚“为什么这样写”。
答案:对。能运行只是第一步,能解释原理、常用操作和常见错误,才说明本页内容进入了可复用能力。
2. 填空题
本页主题是:委托与 Lambda。今天至少要掌握的 3 个点是:
1. 委托为什么存在,它解决什么问题
2. `Func`、`Action`、`Predicate` 分别什么时候用
3. Lambda 的完整读法,不再只会照着写 `=>`答案:以上 3 点必须能用自己的代码跑通,不能只停留在阅读。
3. 流程题
遇到本页相关功能时,先按什么顺序处理?
答案:先看完整例子,确认最终效果;再读原理和名词;然后跟着第三部分从空项目敲代码;最后对照作业答案检查。
4. 找错误题
如果本页代码运行失败,第一步应该做什么?
答案:先看终端或 IDE 里的第一条错误,找到文件名和行号;不要同时改很多地方。再回到本页的“常见错误和修法”表格,对照错误类型逐项排查。
5. 改需求题
在本页完整例子跑通后,至少改一个小需求。
可选改法:
- 改一个字段名称。
- 多加一个校验条件。
- 多输出一行结果。
- 把固定数据改成用户输入。
- 把一次处理改成多条数据处理。
答案标准:修改后能重新运行,并能说明这次修改影响了哪一段逻辑。重点检查:委托为什么存在,它解决什么问题。
上位机专项练习
委托和 Lambda 可以传递报警规则、数据换算和日志动作。
下面 3 个例子都要亲手敲。先运行原代码,再完成每个例子后面的改动任务。
专项例子 1:Func 定义报警规则
Func<double, bool> isAlarm = value => value >= 80;
Console.WriteLine(isAlarm(82.5));
Console.WriteLine(isAlarm(70.0));运行结果或界面效果:
True
False改动任务: 改成低温报警规则 value <= 10。
专项例子 2:Action 传入日志动作
void ReadDevice(Action<string> log)
{
log("开始读取");
log("读取完成");
}
ReadDevice(message => Console.WriteLine($"[设备] {message}"));运行结果或界面效果:
[设备] 开始读取
[设备] 读取完成改动任务: 给日志增加当前时间。
专项例子 3:Lambda 换算工程值
Func<int, double> convert = raw => raw / 4095.0 * 100.0;
Console.WriteLine($"{convert(2048):F2}%");
Console.WriteLine($"{convert(4095):F2}%");运行结果或界面效果:
50.01%
100.00%改动任务: 把量程改成 0-200。
第四部分:作业完整答案
这一部分给出当天作业的完整答案。建议先把题目读完,再直接照着完整代码敲一遍。
作业 1:成绩处理器
要求:
- 准备一组成绩。
- 写一个方法,接收标题、成绩列表、判断规则。
- 分别输出及格成绩、优秀成绩、不及格成绩。
完整答案
List<int> scores = new List<int> { 45, 59, 60, 76, 88, 95 };
void PrintScores(string title, List<int> source, Func<int, bool> rule)
{
Console.WriteLine(title);
foreach (int score in source)
{
if (rule(score))
{
Console.WriteLine(score);
}
}
Console.WriteLine();
}
PrintScores("及格成绩", scores, score => score >= 60);
PrintScores("优秀成绩", scores, score => score >= 90);
PrintScores("不及格成绩", scores, score => score < 60);
这份答案要看懂三点:
PrintScores是固定流程。score => score >= 60是变化规则。rule(score)是执行传进来的规则。
作业 2:价格转换器
要求:
- 准备一组价格。
- 用
Func<decimal, string>把价格转成显示文字。 - 输出每个转换结果。
完整答案
List<decimal> prices = new List<decimal> { 19.9m, 88m, 1299m };
Func<decimal, string> toPriceText = price => $"价格: {price} 元";
foreach (decimal price in prices)
{
string text = toPriceText(price);
Console.WriteLine(text);
}这份答案要看懂一点:
Func<decimal, string>表示输入价格,返回显示文字。
作业 3:日志回调
要求:
- 写一个
SaveCustomer方法。 - 方法内部模拟保存客户。
- 保存完成后调用外面传进来的
Action<string>。
完整答案
void SaveCustomer(string name, Action<string> afterSaved)
{
Console.WriteLine($"开始保存客户: {name}");
Console.WriteLine("写入数据库完成");
afterSaved($"客户 {name} 已保存");
Console.WriteLine();
}
SaveCustomer("张三", message => Console.WriteLine($"日志: {message}"));
SaveCustomer("李四", message => Console.WriteLine($"提醒: {message}"));
这份答案要看懂两点:
SaveCustomer不决定怎么通知。- 调用者通过 Lambda 决定保存完成后做什么。
作业 4:商品规则引擎
要求:
- 定义
Product类。 - 准备商品列表。
- 写一个通用打印方法。
- 使用不同 Lambda 输出不同商品。
完整答案
List<Product> products = new List<Product>
{
new Product { Name = "键盘", Price = 199m, Stock = 8 },
new Product { Name = "鼠标", Price = 89m, Stock = 0 },
new Product { Name = "显示器", Price = 1299m, Stock = 3 },
new Product { Name = "耳机", Price = 299m, Stock = 12 },
new Product { Name = "数据线", Price = 29m, Stock = 50 }
};
void PrintProducts(string title, List<Product> source, Func<Product, bool> rule)
{
Console.WriteLine(title);
foreach (Product product in source)
{
if (rule(product))
{
Console.WriteLine($"{product.Name} | 价格: {product.Price} | 库存: {product.Stock}");
}
}
Console.WriteLine();
}
PrintProducts("有库存的商品", products, product => product.Stock > 0);
PrintProducts("价格不超过 200 的商品", products, product => product.Price <= 200m);
PrintProducts("库存不少于 10 的商品", products, product => product.Stock >= 10);
PrintProducts("有库存并且价格不超过 200 的商品", products, product => product.Stock > 0 && product.Price <= 200m);
class Product
{
public string Name { get; set; } = "";
public decimal Price { get; set; }
public int Stock { get; set; }
}这份答案就是本页的核心总结:
Product是数据结构。PrintProducts是固定流程。Func<Product, bool>是变化规则。- 每个 Lambda 都是一条具体业务规则。
作业 5:把复杂 Lambda 改成普通方法
要求:
- 准备成绩列表。
- 写一个
GetLevel方法,把成绩转成等级文字。 - 用
Select(GetLevel)完成转换。
完整答案
List<int> scores = new List<int> { 45, 60, 75, 88, 96 };
List<string> levels = scores
.Select(GetLevel)
.ToList();
foreach (string level in levels)
{
Console.WriteLine(level);
}
string GetLevel(int score)
{
if (score >= 90)
{
return $"优秀: {score}";
}
if (score >= 80)
{
return $"良好: {score}";
}
if (score >= 60)
{
return $"通过: {score}";
}
return $"未通过: {score}";
}这里故意不用多行 Lambda,因为等级判断已经比较长。规则变长以后,普通方法更容易读,也更容易调试。
本页最后要记住
- 委托让方法可以像变量一样被传递。
- Lambda 是临时写小方法的简短语法。
Func用于有返回值的逻辑。Action用于只执行动作、不返回结果的逻辑。Where需要判断规则,Select需要转换规则。- 重复流程固定、规则经常变化时,就可以考虑委托。