今天学完你要达到这3个结果:
- 会写:委托、
Action/Func、事件、LINQ、泛型 - 会讲:每个知识点的用途、优缺点、面试回答
- 会用:把它们组合进一个小项目(订单管理)
模块1:委托 / Action / Func / Lambda(重点理解"函数也是数据")
1.1 委托是什么?
委托可以理解为:一个"方法类型"。
你可以把方法赋值给变量,再传来传去。
cs
public delegate int Calc(int a, int b);
static int Add(int x, int y) => x + y;
static int Sub(int x, int y) => x - y;
Calc c = Add;
Console.WriteLine(c(3, 2)); // 5
c = Sub;
Console.WriteLine(c(3, 2)); // 1
1.2 为什么需要委托?
- 解耦:调用方不关心具体实现
- 扩展:同一流程可插不同算法
- 常见于:回调、事件、LINQ、异步 API
1.3 Action / Func
Action<T>:有参数、无返回值Func<T1,T2,...,TReturn>:有返回值- 实际项目里比自定义 delegate 用得更多
cs
Action<string> logger = msg => Console.WriteLine($"[LOG]{msg}");
Func<int, int, int> add = (a, b) => a + b;
1.4 Lambda 表达式
Lambda 本质是匿名函数,常和委托搭配。
(x, y) => x + y 就是一个函数。
模块2:事件 event(面试和WPF都高频)
2.1 事件是什么?
事件是"受保护的委托",用于发布订阅模型。
发布者触发事件,订阅者被通知。
cs
public class Stock
{
public string Code { get; set; } = "";
private decimal _price;
public event Action<decimal, decimal>? PriceChanged; // oldPrice, newPrice
public void UpdatePrice(decimal newPrice)
{
var old = _price;
_price = newPrice;
PriceChanged?.Invoke(old, newPrice);
}
}
订阅:
cs
var stock = new Stock { Code = "AAPL" };
stock.PriceChanged += (oldP, newP) =>
{
var diff = newP - oldP;
Console.WriteLine(diff >= 0 ? $"上涨 {diff}" : $"下跌 {diff}");
};
stock.UpdatePrice(100);
stock.UpdatePrice(108);
2.2 event 为什么必要?
如果只是 public Action ...,外部可以直接 Invoke,破坏封装。
event 限制外部只能 += / -=,触发权在类内部。
2.3 面试答法(一句话)
事件是基于委托的发布订阅机制,
event用于限制外部调用权限,保证封装与安全。
模块3:LINQ(今天最重要)
你面试至少要熟练这些:
Where:筛选Select:投影OrderBy/ThenBy:排序GroupBy:分组Any/All:存在性判断FirstOrDefault:取首个或默认Count/Sum/Max/Min:聚合统计
3.1 数据准备
cs
public class Order
{
public int Id { get; set; }
public string CustomerName { get; set; } = "";
public decimal Amount { get; set; }
public DateTime CreatedAt { get; set; }
public bool IsPaid { get; set; }
}
3.2 常见查询写法
cs
var orders = new List<Order>
{
new() { Id=1, CustomerName="Alice", Amount=1200, CreatedAt=DateTime.Today.AddDays(-1), IsPaid=true },
new() { Id=2, CustomerName="Bob", Amount=800, CreatedAt=DateTime.Today, IsPaid=false },
new() { Id=3, CustomerName="Alice", Amount=500, CreatedAt=DateTime.Today, IsPaid=true },
new() { Id=4, CustomerName="Cindy", Amount=2200, CreatedAt=DateTime.Today.AddDays(-2),IsPaid=true }
};
// 1) 金额>1000
var bigOrders = orders.Where(o => o.Amount > 1000).ToList();
// 2) 按客户分组统计总金额
var byCustomer = orders
.GroupBy(o => o.CustomerName)
.Select(g => new
{
Customer = g.Key,
Total = g.Sum(x => x.Amount),
Count = g.Count()
})
.OrderByDescending(x => x.Total)
.ToList();
// 3) 最近一笔订单
var latest = orders.OrderByDescending(o => o.CreatedAt).FirstOrDefault();
// 4) 已支付数量
var paidCount = orders.Count(o => o.IsPaid);
// 5) 是否存在未支付大额订单
var hasRisk = orders.Any(o => !o.IsPaid && o.Amount > 1000);
3.3 LINQ 面试最常问:延迟执行
- 像
Where/Select通常是延迟执行(不马上跑) ToList()/Count()/First()才会触发执行- 面试时你可以说:
"延迟执行可减少不必要计算,也允许链式组合,但要注意数据源变化带来的结果变化。"
模块4:泛型(Generic)与约束
4.1 为什么不用 object?
object 需要强制转换,容易出错、也可能有装箱拆箱性能损耗。
泛型在编译期就类型安全。
4.2 泛型示例
cs
public class Repository<T>
{
private readonly List<T> _items = new();
public void Add(T item) => _items.Add(item);
public List<T> GetAll() => _items;
}
4.3 泛型约束(面试可答)
cs
public class Service<T> where T : class, new()
{
public T Create() => new T();
}
Day2 实战项目
项目名:OrderManager(控制台)
你要建的文件
Order.csIRepository.cs(可选)Repository<T>.csOrderService.csProgram.cs
功能清单(必须)
- 新增订单
- 查询全部订单
- 查询大额订单(阈值输入)
- 按客户分组统计总金额
- 标记订单已支付
- 显示统计:总金额、已支付金额、未支付数
- 订单创建后触发事件(打印日志)
结构建议
OrderService负责业务Repository<T>负责存储Program只负责菜单交互
参考骨架(你可以直接照着写)
cs
public class OrderService
{
private readonly Repository<Order> _repo = new();
public event Action<Order>? OrderCreated;
public void CreateOrder(Order order)
{
_repo.Add(order);
OrderCreated?.Invoke(order);
}
public List<Order> GetAll() => _repo.GetAll();
public List<Order> GetBigOrders(decimal threshold)
=> _repo.GetAll().Where(o => o.Amount > threshold).ToList();
public bool MarkPaid(int id)
{
var order = _repo.GetAll().FirstOrDefault(o => o.Id == id);
if (order == null) return false;
order.IsPaid = true;
return true;
}
}
今日面试题
1) 委托和事件区别?
- 委托是方法类型;事件是对委托的封装,限制外部只能订阅/退订,不能触发。
2) 为什么优先用 Action/Func?
- 内置、简洁、可读性好,减少重复定义 delegate。
3) Any 和 Count()>0 哪个好?
Any更好,找到一个就返回;Count常需遍历更多。
4) First 和 FirstOrDefault 区别?
First找不到抛异常;FirstOrDefault返回默认值(引用类型为 null)。
5) IEnumerable vs IQueryable?
IEnumerable在内存中执行;IQueryable可转为表达式树,由数据库端执行(如 EF)。
6) 泛型的价值?
- 复用 + 类型安全 + 减少装箱拆箱,提高性能和可维护性。