今天学完你要达到这3个结果:

  • 会写:委托、Action/Func、事件、LINQ、泛型
  • 会讲:每个知识点的用途、优缺点、面试回答
  • 会用:把它们组合进一个小项目(订单管理)

模块1:委托 / Action / Func / Lambda(重点理解“函数也是数据”)

1.1 委托是什么?

委托可以理解为:一个“方法类型”。
你可以把方法赋值给变量,再传来传去。

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 用得更多
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 事件是什么?

事件是“受保护的委托”,用于发布订阅模型。
发布者触发事件,订阅者被通知。

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);
    }
}

订阅:

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 数据准备

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 常见查询写法

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 泛型示例

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 泛型约束(面试可答)

public class Service<T> where T : class, new()
{
    public T Create() => new T();
}

    Day2 实战项目

    项目名:OrderManager(控制台)

    你要建的文件

    • Order.cs
    • IRepository.cs(可选)
    • Repository<T>.cs
    • OrderService.cs
    • Program.cs

    功能清单(必须)

    1. 新增订单
    2. 查询全部订单
    3. 查询大额订单(阈值输入)
    4. 按客户分组统计总金额
    5. 标记订单已支付
    6. 显示统计:总金额、已支付金额、未支付数
    7. 订单创建后触发事件(打印日志)

    结构建议

    • OrderService 负责业务
    • Repository<T> 负责存储
    • Program 只负责菜单交互

    参考骨架(你可以直接照着写)

    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) 泛型的价值?

    • 复用 + 类型安全 + 减少装箱拆箱,提高性能和可维护性。

    更多推荐