第 9 章:委托、事件与表达式树

jerry北京市2026年4月20日C# 6 次阅读 约 23 分钟
第 9 章:委托、事件与表达式树

深入理解委托的底层实现(MulticastDelegate、调用列表)、事件机制、Expression<T> 与 Func<T> 的区别,以及表达式树在 EF Core 和动态查询中的应用。


9.1 委托的底层

委托本质上是一个继承自 MulticastDelegate 的类:

// 你写的代码
delegate int MathOp(int a, int b);

// 编译器生成的(简化)
class MathOp : MulticastDelegate
{
    // 构造函数:绑定目标对象和方法指针
    public MathOp(object target, IntPtr method) { }

    // 同步调用
    public virtual int Invoke(int a, int b) { }

    // 异步调用(已过时)
    public virtual IAsyncResult BeginInvoke(int a, int b, AsyncCallback cb, object state) { }
    public virtual int EndInvoke(IAsyncResult result) { }
}

委托的内存结构

MulticastDelegate:
┌─────────────────────────┐
│ _target    → 目标对象    │  实例方法时指向对象,静态方法时为 null
│ _methodPtr → 方法指针    │  指向要调用的方法
│ _invocationList → 调用列表│  多播时指向 Delegate[] 数组
└─────────────────────────┘
// 实例方法委托
var obj = new Calculator();
Func<int, int, int> add = obj.Add;
// _target = obj, _methodPtr = Calculator.Add 的地址

// 静态方法委托
Func<int, int, int> staticAdd = Calculator.StaticAdd;
// _target = null, _methodPtr = Calculator.StaticAdd 的地址

// Lambda(编译器生成闭包类)
int factor = 10;
Func<int, int> multiply = x => x * factor;
// _target = 编译器生成的闭包对象, _methodPtr = 闭包类的方法

9.2 多播委托

一个委托可以绑定多个方法:

Action<string> log = Console.WriteLine;
log += msg => File.AppendAllText("log.txt", msg + "\n");
log += msg => Debug.WriteLine(msg);

// 调用时按顺序执行所有方法
log("Hello"); // 输出到控制台 + 写文件 + Debug 输出

// 移除
log -= Console.WriteLine;

// 获取调用列表
Delegate[] list = log.GetInvocationList();
Console.WriteLine(list.Length); // 2

多播委托的返回值问题

Func<int> getNumber = () => 1;
getNumber += () => 2;
getNumber += () => 3;

int result = getNumber(); // 只返回最后一个:3

// 如果需要所有返回值,手动遍历
foreach (Func<int> d in getNumber.GetInvocationList())
{
    Console.WriteLine(d()); // 1, 2, 3
}

9.3 Action、Func、Predicate

.NET 内置的泛型委托,避免自定义委托类型:

// Action:无返回值
Action                    action0 = () => Console.WriteLine("无参数");
Action<int>               action1 = x => Console.WriteLine(x);
Action<int, string>       action2 = (x, s) => Console.WriteLine($"{x}: {s}");

// Func:有返回值(最后一个类型参数是返回类型)
Func<int>                 func0 = () => 42;
Func<int, string>         func1 = x => x.ToString();
Func<int, int, bool>      func2 = (a, b) => a > b;

// Predicate:返回 bool 的特殊委托
Predicate<int> isEven = x => x % 2 == 0;
// 等价于 Func<int, bool>

// 在 LINQ 中的使用
var numbers = new[] { 1, 2, 3, 4, 5 };
var evens = numbers.Where(x => x % 2 == 0);        // Func<int, bool>
var doubled = numbers.Select(x => x * 2);           // Func<int, int>
numbers.ToList().ForEach(x => Console.WriteLine(x)); // Action<int>

9.4 事件机制

事件是对委托的封装,限制外部只能 +=-=

class Button
{
    // 事件声明(编译器生成 add/remove 访问器)
    public event EventHandler<ClickEventArgs> Clicked;

    // 触发事件
    protected virtual void OnClicked(ClickEventArgs e)
    {
        Clicked?.Invoke(this, e);  // 线程安全的调用方式
    }

    public void SimulateClick()
    {
        OnClicked(new ClickEventArgs(DateTime.Now));
    }
}

record ClickEventArgs(DateTime ClickTime);

// 使用
var button = new Button();
button.Clicked += (sender, e) => Console.WriteLine($"点击时间: {e.ClickTime}");
button.SimulateClick();

// 不能这样做(编译错误):
// button.Clicked = null;        // 不能直接赋值
// button.Clicked(button, args); // 不能从外部触发

自定义 add/remove 访问器

class ThreadSafePublisher
{
    private EventHandler? _myEvent;
    private readonly object _lock = new();

    public event EventHandler MyEvent
    {
        add
        {
            lock (_lock) { _myEvent += value; }
        }
        remove
        {
            lock (_lock) { _myEvent -= value; }
        }
    }

    protected void OnMyEvent()
    {
        EventHandler? handler;
        lock (_lock) { handler = _myEvent; }
        handler?.Invoke(this, EventArgs.Empty);
    }
}

弱事件模式(避免内存泄漏)

class WeakEventManager<TEventArgs>
{
    private readonly List<WeakReference<EventHandler<TEventArgs>>> _handlers = new();

    public void AddHandler(EventHandler<TEventArgs> handler)
    {
        _handlers.Add(new WeakReference<EventHandler<TEventArgs>>(handler));
    }

    public void Raise(object sender, TEventArgs args)
    {
        for (int i = _handlers.Count - 1; i >= 0; i--)
        {
            if (_handlers[i].TryGetTarget(out var handler))
                handler(sender, args);
            else
                _handlers.RemoveAt(i); // 清理已回收的引用
        }
    }
}

9.5 Expression<T> vs Func<T>

这是理解 EF Core 查询的关键:

// Func<T>:编译后的可执行代码(IL 指令)
Func<int, bool> func = x => x > 5;
// func 是一个方法指针,直接调用执行

// Expression<T>:表达式树(数据结构,描述代码的 AST)
Expression<Func<int, bool>> expr = x => x > 5;
// expr 是一棵树,可以被分析、修改、翻译
Expression<Func<int, bool>> expr = x => x > 5;

表达式树结构:
        Lambda
       /      \
  Parameter   GreaterThan
    (x)       /          \
          Parameter    Constant
            (x)          (5)

为什么 EF Core 需要 Expression?

// 使用 Expression<Func<>>:翻译为 SQL
IQueryable<User> query = dbContext.Users;
query.Where(u => u.Age > 18);
// EF Core 分析表达式树,生成:SELECT * FROM Users WHERE Age > 18

// 使用 Func<>:在内存中过滤(性能灾难)
IEnumerable<User> all = dbContext.Users;
all.Where(u => u.Age > 18);
// 先加载所有数据到内存,再过滤

9.6 表达式树的构建与编译

手动构建表达式树

// 构建 x => x * 2 + 1
var param = Expression.Parameter(typeof(int), "x");
var multiply = Expression.Multiply(param, Expression.Constant(2));
var add = Expression.Add(multiply, Expression.Constant(1));
var lambda = Expression.Lambda<Func<int, int>>(add, param);

// 编译为可执行的委托
Func<int, int> compiled = lambda.Compile();
Console.WriteLine(compiled(5)); // 11

// 查看表达式
Console.WriteLine(lambda.ToString()); // x => ((x * 2) + 1)

动态查询构建

// 根据条件动态构建 Where 表达式
static Expression<Func<T, bool>> BuildFilter<T>(
    string propertyName, object value)
{
    var param = Expression.Parameter(typeof(T), "x");
    var property = Expression.Property(param, propertyName);
    var constant = Expression.Constant(value);
    var equal = Expression.Equal(property, constant);
    return Expression.Lambda<Func<T, bool>>(equal, param);
}

// 使用
var filter = BuildFilter<User>("Name", "张三");
var users = dbContext.Users.Where(filter).ToList();
// 生成 SQL: SELECT * FROM Users WHERE Name = '张三'

组合表达式

static Expression<Func<T, bool>> And<T>(
    Expression<Func<T, bool>> left,
    Expression<Func<T, bool>> right)
{
    var param = Expression.Parameter(typeof(T), "x");
    var body = Expression.AndAlso(
        Expression.Invoke(left, param),
        Expression.Invoke(right, param));
    return Expression.Lambda<Func<T, bool>>(body, param);
}

// 更高效的方式:替换参数
static Expression<Func<T, bool>> AndAlso<T>(
    Expression<Func<T, bool>> left,
    Expression<Func<T, bool>> right)
{
    var param = left.Parameters[0];
    var body = Expression.AndAlso(
        left.Body,
        new ParameterReplacer(right.Parameters[0], param).Visit(right.Body));
    return Expression.Lambda<Func<T, bool>>(body, param);
}

// 使用
Expression<Func<User, bool>> ageFilter = u => u.Age > 18;
Expression<Func<User, bool>> nameFilter = u => u.Name.Contains("张");
var combined = AndAlso(ageFilter, nameFilter);
var result = dbContext.Users.Where(combined).ToList();

9.7 实战:通用排序扩展

public static class QueryableExtensions
{
    public static IQueryable<T> OrderByProperty<T>(
        this IQueryable<T> source,
        string propertyName,
        bool descending = false)
    {
        var param = Expression.Parameter(typeof(T), "x");
        var property = Expression.Property(param, propertyName);
        var lambda = Expression.Lambda(property, param);

        string methodName = descending ? "OrderByDescending" : "OrderBy";

        var method = typeof(Queryable).GetMethods()
            .First(m => m.Name == methodName && m.GetParameters().Length == 2)
            .MakeGenericMethod(typeof(T), property.Type);

        return (IQueryable<T>)method.Invoke(null, [source, lambda])!;
    }
}

// 使用:根据前端传来的字段名排序
var sorted = dbContext.Users
    .OrderByProperty("Age", descending: true)
    .ToList();

9.8 面试要点

  1. 委托的底层实现?

    • 继承自 MulticastDelegate,包含 _target(目标对象)、_methodPtr(方法指针)、_invocationList(多播列表)
  2. 事件和委托的区别?

    • 事件是委托的封装,外部只能 += 和 -=,不能直接赋值或调用
  3. Expression<Func<T>> 和 Func<T> 的区别?

    • Func 是编译后的可执行代码,Expression 是描述代码的数据结构(AST)
    • EF Core 的 IQueryable 需要 Expression 来翻译为 SQL
  4. 为什么 IQueryable.Where 接收 Expression 而不是 Func?

    • 因为需要分析表达式树并翻译为 SQL,Func 只能在内存中执行
  5. 如何动态构建查询条件?

    • 使用 Expression API 手动构建表达式树,或使用 LINQKit 等库

练习

  1. 实现一个多播委托,收集所有返回值(而不是只取最后一个)
  2. 实现一个事件总线(EventBus),支持按类型订阅和发布
  3. 使用 Expression API 构建一个动态过滤器,支持 Equal、Contains、GreaterThan 等操作
  4. 对比 Expression.Compile() 和直接使用 Func 的性能差异

← 上一章:Channel 与并发集合 | 下一章:反射与 Source Generator →

评论

登录 后发表评论

暂无评论