委托、事件与 Lambda

翻译整理自 Delegates 与 Events。

1. 委托 delegate

委托是类型安全的函数指针:

public delegate int MathOp(int a, int b);

MathOp add = (a, b) => a + b;
MathOp sub = (a, b) => a - b;

Console.WriteLine(add(1, 2));   // 3

2. 内置委托类型

类型 形式
Action 无参无返回
Action<T> 一参无返回
Action<T1,T2> 两参无返回
Func<TR> 无参返回 TR
Func<T,TR> 一参返回 TR
Predicate<T> 一参返回 bool
Action<string> log = s => Console.WriteLine(s);
Func<int, int, int> mul = (a, b) => a * b;
Predicate<int> isPos = n => n > 0;

3. Lambda 表达式

Func<int, int> sq = x => x * x;
Func<int, int, int> add = (a, b) => a + b;
Action<int> print = x => Console.WriteLine(x);

// 语句 lambda
Func<int, int> abs = x =>
{
    if (x < 0) return -x;
    return x;
};

闭包

int factor = 10;
Func<int, int> mul = x => x * factor;   // 捕获外部变量
factor = 20;
Console.WriteLine(mul(5));              // 100(按引用捕获)

4. 多播委托

Action<string> log = Console.WriteLine;
log += x => File.AppendAllText("a.log", x);
log("hello");                  // 输出 + 写文件
log -= ...;

5. 事件 event

事件是基于委托的发布订阅模式:

public class Button
{
    public event EventHandler Clicked;

    public void Click() => Clicked?.Invoke(this, EventArgs.Empty);
}

var btn = new Button();
btn.Clicked += (s, e) => Console.WriteLine("clicked");
btn.Click();

自定义事件参数:

public class PriceChangedEventArgs : EventArgs
{
    public decimal OldPrice { get; set; }
    public decimal NewPrice { get; set; }
}

public event EventHandler<PriceChangedEventArgs> PriceChanged;

PriceChanged += (s, e) =>
    Console.WriteLine($"{e.OldPrice} → {e.NewPrice}");

6. 表达式树 Expression

using System.Linq.Expressions;

Expression<Func<int, int>> expr = x => x * 2;
var compiled = expr.Compile();
Console.WriteLine(compiled(3));   // 6

EF Core 等用表达式树把 C# lambda 翻译为 SQL。

7. 局部函数 vs Lambda

public IEnumerable<int> Get()
{
    return Iterator();
    IEnumerable<int> Iterator()
    {
        for (int i = 0; i < 10; i++) yield return i;
    }
}

局部函数不分配闭包对象,性能略好。

8. Func 与 Action 反射

MethodInfo CallDelegate(MethodInfo m, object target, object[] args)
    => (MethodInfo)m.Invoke(target, args);

小结

委托是 C# 一切回调和事件的基础,lambda 让函数式编程风格更自然,表达式树让 ORM 可以把 C# 翻译为 SQL。