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

深入理解委托的底层实现(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 面试要点
-
委托的底层实现?
- 继承自 MulticastDelegate,包含 _target(目标对象)、_methodPtr(方法指针)、_invocationList(多播列表)
-
事件和委托的区别?
- 事件是委托的封装,外部只能 += 和 -=,不能直接赋值或调用
-
Expression<Func<T>> 和 Func<T> 的区别?
- Func 是编译后的可执行代码,Expression 是描述代码的数据结构(AST)
- EF Core 的 IQueryable 需要 Expression 来翻译为 SQL
-
为什么 IQueryable.Where 接收 Expression 而不是 Func?
- 因为需要分析表达式树并翻译为 SQL,Func 只能在内存中执行
-
如何动态构建查询条件?
- 使用 Expression API 手动构建表达式树,或使用 LINQKit 等库
练习
- 实现一个多播委托,收集所有返回值(而不是只取最后一个)
- 实现一个事件总线(EventBus),支持按类型订阅和发布
- 使用 Expression API 构建一个动态过滤器,支持 Equal、Contains、GreaterThan 等操作
- 对比 Expression.Compile() 和直接使用 Func 的性能差异
评论
登录 后发表评论
暂无评论