📚 **Day 5 · LINQ 延迟执行与表达式树**
5推送消息数
20048字符数
2026-08-04教学日期
<!-- message_id: om_x100b68268e26e0a0b25ab86a4c66e15 -->
📚 **Day 5 · LINQ 延迟执行与表达式树**
主题:LINQ 不是单一技术,而是**两层抽象的协同**:IEnumerable<T> + 迭代器模式(CPU 执行) vs IQueryable<T> + 表达式树(第三方 Provider 翻译)。理解这层抽象是所有 LINQ 高级话题的基础。
───
一、今日知识点(4 维度)
1️⃣ 是什么
LINQ(Language Integrated Query)是**两个独立概念的统一**:
• **查询语法层**:from x in src where ... select ... 会被 C# 编译器重写为链式方法调用(src.Where(...).Select(...))。这是**纯语法糖**,与手写链式调用生成的 IL 完全相同。
• **执行抽象层**:
• IEnumerable<T>(System.Linq.Enumerable 命名空间):本地内存查询,CPU 执行 MoveNext()
• IQueryable<T>(System.Linq.Queryable 命名空间):远程查询,Provider 把查询翻译成目标语言(SQL / HTTP / MongoDB 查询)
**延迟执行(Deferred Execution)的本质**:链式调用 Where / Select / OrderBy **不执行任何过滤或映射操作**,只是返回一个查询描述符(包装迭代器);真正的执行发生在第一次 MoveNext() 时。
2️⃣ 为什么
设计动机有三个层次:
• **组合性**:像 SQL 一样拼装查询,分步构建,延迟绑定执行时机
• **可翻译性**:把"查询"抽象成**数据结构**(表达式树),让第三方 Provider 能"读懂"你的意图并翻译成目标语言——这是 EF Core / LINQ to SQL / MongoDB.Driver 能存在的原因
• **数据源无关**:同一套查询语法可以操作集合、数据库、XML、DataSet、Web API
3️⃣ 怎么用
**场景 A:LINQ to Objects(CPU 执行)**
IEnumerable<int> query = Enumerable.Range(1, 10)
.Where(x => x % 2 == 0)
.Select(x => x * x);
// query 在这里只是"查询描述符",没执行
foreach (var n in query)
Console.WriteLine(n); // 第一次 MoveNext() 才执行
**场景 B:LINQ to Entities(Provider 翻译)**
Expression<Func<User, bool>> pred = u => u.Age > 18 && u.Name.StartsWith("张");
IQueryable<User> users = db.Users.Where(pred).OrderBy(u => u.Id);
// db.Provider 把表达式树翻译成 SQL:
// SELECT * FROM Users WHERE Age > 18 AND Name LIKE '张%' ORDER BY Id
**场景 C:强制立即执行(终结符)**
var list = query.ToList(); // 物化为 List<T>
var first = query.FirstOrDefault(); // 单值提取
var count = query.Count(); // 聚合(IQueryable 翻译为 COUNT(*))
var arr = query.ToArray();
**场景 D:自定义 Provider(ExpressionVisitor)**
public class MyTranslator : ExpressionVisitor
{
protected override Expression VisitMethodCall(MethodCallExpression node)
{
// 拦截 Queryable.Where / Select / OrderBy / Join 等调用
// 重写为自定义执行逻辑(翻译成 SQL / 远程调用)
return base.VisitMethodCall(node);
}
}
var rewritten = (Expression<Func<T, bool>>)visitor.Visit(expr);
**关键 Insight**:IEnumerable<T> 和 IQueryable<T> 的所有扩展方法**签名相同**(Func<TSource, bool> vs Expression<Func<TSource, bool>>),但底层执行模型天差地别——这就是为什么 LINQ 能"一套语法两个世界"。
4️⃣ 常见误区
| 误区 | 真相 |
| ------------------------------------------- | ----------------------------------------------------------------------------------- |
| Where() 会立刻过滤 | ❌ 只返回包装迭代器,调用 MoveNext() 才执行 |
| Func<T,bool> 和 Expression<Func<T,bool>> 可互转 | ❌ Func → Expression 需要 Expression.Compile() 走反射 Emit,反向才能直接转(隐式转换) |
| AsEnumerable() 和 AsQueryable() 等价 | ❌ 完全不同:前者下沉到 LINQ to Objects(CPU 内存过滤),后者升级到 LINQ to Provider(可翻译为 SQL) |
| 多次枚举 IEnumerable 没成本 | ❌ LINQ to Objects 重跑整个管道;IQueryable 可能每次重新查数据库(N+1 隐患) |
| Count() 总返回集合大小 | ❌ IQueryable.Count() 翻译成 SELECT COUNT(*)(DB 端 O(1));IEnumerable.Count() 是 O(n) 内存遍历 |
5️⃣ 关联知识(构建知识网络)
• **与泛型(Day 2)**:LINQ 是泛型扩展方法的集大成——Where<TSource> / Select<TSource, TResult> / OrderBy<TSource, TKey> 都是泛型方法,延迟执行依赖泛型委托 Func<TSource, bool> / Func<TSource, TResult>
• **与 async/await(Day 4)**:C# 8+ 引入 IAsyncEnumerable<T>(await foreach),是**延迟执行 + 异步流**的结合——底层用 ConfiguredCancelableAsyncEnumerable<T> 包装,每次 MoveNextAsync() 都是一次状态机推进
• **与闭包(Day 3)**:lambda 中捕获的外部变量会让每次查询重新计算,且闭包对象本身有内存分配成本
• **与反射/Emit**:表达式树 → IL 委托的转换(LambdaExpression.Compile())底层就是 System.Reflection.Emit + DynamicMethod
---
<!-- message_id: om_x100b68268f8da0a0b3e45fd8bce1113 -->
📝 **Day 5 · 3 道递进面试题**
难度递进:基础概念 → 原理与辨析 → 实战与深度
Q1(基础概念)
请简述 LINQ 中"延迟执行"的本质,并判断以下 5 个操作哪些是延迟、哪些是立即执行:
var src = new List<int> { 1, 2, 3, 4, 5 };
var q1 = src.Where(x => x > 2);
var q2 = src.Where(x => x > 2).ToList();
var q3 = src.Where(x => x > 2).Count();
var q4 = src.Where(x => x > 2).First();
var q5 = src.Where(x => { Console.WriteLine(x); return x > 2; });
补充:如果对 q1 和 q5 各执行两次 foreach 循环,Console.WriteLine(x) 会输出多少次?为什么?
Q2(原理与辨析)
Expression<Func<T, bool>> 与 Func<T, bool> 在 **IL 层** 和 **运行时表现** 上有何本质区别?EF Core / LINQ to SQL 是如何利用表达式树实现 SQL 翻译的?请用 ExpressionVisitor 的伪代码说明 VisitMethodCall / VisitBinary / VisitMember 三个核心方法的工作原理。
Q3(实战与深度 · WPF MVVM 场景)
在企业级 WPF MVVM 项目中,需要实现"**动态过滤 UI**":
- 用户在前端勾选条件(年龄段 Age ∈ [18, 35]、城市 City = "北京"、状态 Status = Active、支持 AND/OR 嵌套)
- 后端要把这些条件翻译成 SQL 查询(千万级数据表,不能在内存过滤)
- 需要运行时类型安全(前端字段名不能写错,否则立即报错)
**任务**:
1. 设计一套基于 ExpressionVisitor 的查询树构建方案(核心代码 + 架构图)
2. 对比至少 2 种备选方案(动态 LINQ / 手写 SQL 拼接 / DynamicExpresso / Lambda 拼接)的优劣
3. 给出你的最终选择和理由
**附加要求**:方案必须支持 string.Contains 翻译为 SQL LIKE、日期范围 [from, to) 翻译为 BETWEEN、IN (1,2,3) 翻译为 SQL IN。
---
<!-- message_id: om_x100b68268cfd8474b1f60679ee325e6 -->
🎯 **Day 5 · 全面复盘讲解(Q1 + Q2)**
───
Q1 答案要点
**延迟执行本质**:Where / Select / OrderBy / GroupBy 等方法返回的是**包装迭代器**(WhereEnumerableIterator<T> / SelectIterator<T> 等内部类,继承自 Iterator<T>),只在 foreach / ToList() / First() 触发 GetEnumerator().MoveNext() 时才真正执行过滤 / 映射逻辑。
**判断 5 个操作**:
| 操作 | 类型 | 原因 |
| ---------------------------------------------- | ----- | ------------------------------- |
| q1 = src.Where(...) | 🟡 延迟 | 纯 Where,返回 WhereListIterator<T> |
| q2 = ... .ToList() | 🟢 立即 | ToList() 是终结符,触发完整物化 |
| q3 = ... .Count() | 🟢 立即 | Count() 是终结符 |
| q4 = ... .First() | 🟢 立即 | First() 是终结符 |
| q5 = ... Where(x => { WriteLine; return ... }) | 🟡 延迟 | lambda 体是惰性的,每次枚举重新执行 |
**补充问题答案**:对 q1 和 q5 各执行两次 foreach,Console.WriteLine(x) 会输出 **6 次**(q1 两次都输出 3,4,5;q5 两次都输出 1,2,3,4,5)。原因:lambda 是延迟执行的——**每次 MoveNext() 都会重新调用 lambda 评估谓词**,打印逻辑随之执行。
**得分点**:
• ✅ 区分"延迟 vs 立即"的根本标志:**是否触发 GetEnumerator().MoveNext()**
• ✅ 知道 ToList() / Count() / First() / ToArray() / Single() / Any() / Sum() / GroupBy().ToDictionary() 等是终结符
• ✅ 知道 lambda 是惰性的,捕获的副作用(如 WriteLine)会随每次重枚举重新执行
• ❌ 易错点:以为 var q = src.Where(...) "准备好了数据"
───
Q2 答案要点
IL 层与运行时表现对比
| 维度 | Func<T, bool> | Expression<Func<T, bool>> |
| -------- | ------------------- | ---------------------------------------------------------------------------- |
| 类型本质 | 委托(指向 IL 方法的函数指针) | 表达式树(数据结构,内存中是 Expression 节点树) |
| C# 编译器生成 | ldftn + newobj 委托构造 | Expression.Lambda + 一系列 Expression.Constant / Expression.GreaterThan 等节点构造调用 |
| 运行时形态 | 直接调用原生 IL,最快 | 不 Compile() 时是可遍历的数据;Compile() 后才生成 IL |
| 可分析性 | ❌ 黑盒,只能 invoke | ✅ 可遍历、修改、重写、序列化、远程传输 |
| 内存占用 | 一个委托对象(~24 字节) | 一棵 Expression 节点树(数十到数百字节) |
EF Core / LINQ to SQL 翻译原理(5 步)
1. db.Users.Where(expr) 返回 IQueryable<User>,内部把 expr.Body(一棵 Expression 树)交给 IQueryProvider
2. Provider 的 QueryCompiler 调用 ExpressionVisitor 深度遍历树(访问者模式)
3. VisitMethodCall 拦截 Queryable.Where / Queryable.Select / Queryable.OrderBy / Queryable.Join 等已知方法
4. VisitBinary 把 > == < && 翻译成 SQL 运算符
5. VisitMember 把 u.Age 翻译成列名;VisitConstant 提取参数值(参数化防注入)
伪代码(精简版 ExpressionVisitor)
public class SqlTranslator : ExpressionVisitor
{
private readonly StringBuilder _sql = new();
private readonly Dictionary<string, object> _params = new();
// 拦截 Queryable.Where / Select / OrderBy / Join 等
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.DeclaringType == typeof(Queryable))
{
switch (node.Method.Name)
{
case "Where":
Visit(node.Arguments[0]); // 先访问数据源
_sql.Append(" WHERE ");
Visit(node.Arguments[1]); // 再访问谓词
break;
case "OrderBy":
Visit(node.Arguments[0]);
_sql.Append(" ORDER BY ");
Visit(node.Arguments[1]);
break;
// Select / Join / GroupBy 略...
}
return node;
}
// 处理 string.Contains / StartsWith → LIKE
if (node.Method.Name == "Contains" && node.Method.DeclaringType == typeof(string))
{
Visit(node.Object);
_sql.Append(" LIKE '%' + ");
Visit(node.Arguments[0]);
_sql.Append(" + '%'");
return node;
}
return base.VisitMethodCall(node);
}
// 拦截 > / == / < / && / ||
protected override Expression VisitBinary(BinaryExpression node)
{
Visit(node.Left);
_sql.Append(node.NodeType switch
{
ExpressionType.GreaterThan => " > ",
ExpressionType.Equal => " = ",
ExpressionType.LessThan => " < ",
ExpressionType.AndAlso => " AND ",
ExpressionType.OrElse => " OR ",
_ => " ? "
});
Visit(node.Right);
return node;
}
// 拦截 x.Age → 列名
protected override Expression VisitMember(MemberExpression node)
{
_sql.Append(node.Member.Name); // 简化:直接用属性名作列名
return node;
}
// 拦截常量 → 参数化
protected override Expression VisitConstant(ConstantExpression node)
{
var name = $"@p{_params.Count}";
_params[name] = node.Value;
_sql.Append(name);
return node;
}
}
**进阶**:
• EF Core 内部用的是 Microsoft.EntityFrameworkCore.Query.Query 命名空间下的 RelationalSqlTranslatingExpressionVisitor(2000+ 行),处理子查询、关联、Include、GroupBy 等复杂场景
• IQueryProvider.Execute 是入口:返回 IQueryable<T> 时返回新查询描述符;返回 T 时执行翻译并调用 [ADO.NET](ADO.NET)
**易错点**:
• ❌ 以为 Func 和 Expression 只是"语法差异"——本质是**IL vs 数据结构**
• ❌ 不知道 Expression.Compile() 是有成本的(反射 Emit),不适合热路径
• ❌ 不知道 EF Core 默认 IQueryable.Count() 翻译为 SQL COUNT(*),不是加载到内存再数
---
<!-- message_id: om_x100b68268de3b4a0b3e8d04fc94cc99 -->
🎯 **Day 5 · 全面复盘讲解(Q3 · 实战)**
───
Q3 答案要点
方案:基于 ExpressionVisitor 的动态过滤查询构建器
**架构图**:
[UI 过滤规则] → [FilterBuilder<T>.Build] → [Expression<Func<T, bool>>]
↓
[EF Core / IQueryable]
↓
[SQL 翻译]
**核心代码(4 层)**:
**Layer 1 · UI 规则数据模型(POJO)**
public enum FilterLogic { And, Or }
public enum FilterOp { Eq, Ne, Gt, Ge, Lt, Le, Contains, StartsWith, In, Between }
public record FilterRule(
string FieldName, // "Age" / "City" / "Status"
FilterOp Op,
object? Value,
object? Value2, // for Between
FilterLogic Logic = FilterLogic.And
);
**Layer 2 · 强类型字段引用(避免魔法字符串)**
public static class FilterBuilder<T>
{
public static Expression<Func<T, bool>> Build(IList<FilterRule> rules)
{
var param = Expression.Parameter(typeof(T), "x");
Expression? body = null;
foreach (var rule in rules)
{
// ⚠️ 运行时校验字段存在性——传错字段名立即抛异常
var propInfo = typeof(T).GetProperty(rule.FieldName)
?? throw new ArgumentException($"字段不存在: {rule.FieldName}");
var member = Expression.Property(param, propInfo);
var comparison = BuildComparison(member, rule);
body = body is null
? comparison
: rule.Logic == FilterLogic.And
? Expression.AndAlso(body, comparison)
: Expression.OrElse(body, comparison);
}
return Expression.Lambda<Func<T, bool>>(body!, param);
}
private static Expression BuildComparison(MemberExpression member, FilterRule rule)
{
return rule.Op switch
{
FilterOp.Eq => Expression.Equal(member, Expression.Constant(rule.Value)),
FilterOp.Gt => Expression.GreaterThan(member, Expression.Constant(rule.Value)),
FilterOp.Contains => Expression.Call(member, "Contains", null, Expression.Constant(rule.Value)),
FilterOp.In => BuildIn(member, (IEnumerable)rule.Value!),
FilterOp.Between => Expression.AndAlso(
Expression.GreaterThanOrEqual(member, Expression.Constant(rule.Value)),
Expression.LessThan(member, Expression.Constant(rule.Value2))),
_ => throw new NotSupportedException($"不支持的操作: {rule.Op}")
};
}
private static Expression BuildIn(MemberExpression member, IEnumerable values)
{
var list = values.Cast<object>().ToList();
var containsMethod = typeof(List<object>).GetMethod("Contains", new[] { typeof(object) })!;
return Expression.Call(Expression.Constant(list), containsMethod, member);
}
}
**Layer 3 · WPF MVVM 中的使用**
public class UserFilterViewModel : ObservableObject
{
[ObservableProperty] private int ageFrom = 18, ageTo = 35;
[ObservableProperty] private string? city;
public IRelayCommand ApplyFilterCommand { get; }
public UserFilterViewModel(IDbContextFactory<AppDbContext> dbFactory)
{
ApplyFilterCommand = new RelayCommand(() =>
{
var rules = new List<FilterRule>
{
new("Age", FilterOp.Between, AgeFrom, AgeTo, FilterLogic.And),
new("City", FilterOp.Eq, City, null, FilterLogic.And),
};
var expr = FilterBuilder<User>.Build(rules);
using var db = dbFactory.CreateDbContext();
var sql = db.Users.Where(expr).ToQueryString();
// 输出: SELECT [u].[Id], [u].[Name], [u].[Age], [u].[City]
// FROM [Users] AS [u]
// WHERE [u].[Age] >= 18 AND [u].[Age] < 35 AND [u].[City] = @__city_0
// ⚠️ ToQueryString() 是 EF Core 5+ 的方法,可以查看生成的 SQL
Users = new ObservableCollection<User>(db.Users.Where(expr).ToList());
});
}
}
**Layer 4 · 扩展能力(自定义 ExpressionVisitor)**
如果 EF Core 默认翻译不能覆盖某些场景(如 string.Contains → LIKE),可以写自定义 Visitor:
public class CustomContainsVisitor : ExpressionVisitor
{
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.Name == "Contains" && node.Method.DeclaringType == typeof(string))
{
// 重写为 SQL LIKE 友好的形式
return Expression.Call(
node.Object!,
typeof(string).GetMethod(nameof(string.Contains), new[] { typeof(string) })!,
node.Arguments[0]);
}
return base.VisitMethodCall(node);
}
}
───
对比 2 种备选方案
| 方案 | 优点 | 缺点 | 适用场景 |
| ---------------------------------- | ----------------------------------- | --------------------------------------------- | ------------- |
| 动态 LINQ(System.Linq.Dynamic) | 字符串 DSL:"Age > 18 and City == '北京'" | 字段名是字符串无类型检查、易注入、性能差(运行时解析字符串) | 内部工具、低复杂度、原型 |
| 手写 SQL 拼接 | 灵活度最高、可利用数据库特性 | SQL 注入风险、字段映射重复、难维护、不可测试 | 极致性能场景、DBA 维护 |
| DynamicExpresso | 运行时编译表达式、API 友好 | 仍是 Func 不是 Expression,无法翻译到 SQL,会被 EF 降级到内存过滤 | 内存计算场景 |
| Lambda 拼接(Expression.AndAlso 手动组合) | 最轻量、无需 Visitor | 嵌套深时难维护、字段名仍是字符串 | 简单条件(<5 个) |
| ExpressionVisitor 自建(本方案) | ✅ 类型安全、可翻译 SQL、可扩展、高性能 | 需要写 Visitor 代码、学习成本 | 生产级首选 |
───
最终选择:ExpressionVisitor + 强类型字段引用
**理由**:
1. **类型安全**:typeof(T).GetProperty(name) 在运行期校验字段存在性,前端传错字段立即报错
2. **可翻译到 SQL**:表达式树结构天然适合 EF Provider 翻译,**不会降级到内存过滤**
3. **可扩展**:自定义 ExpressionVisitor 可以拦截 string.Contains / DateTime.CompareTo / 自定义方法并翻译为 SQL
4. **可测试**:表达式树是数据(Expression<Func<T, bool>>),可以序列化、单元测试、对比 SQL 输出
5. **性能**:编译期生成的 IL(Compile() 后)跟手写 lambda 一样快,零运行时解析开销
6. **企业级**:ToQueryString() 可以输出实际 SQL 供 DBA review,团队协作友好
**对比 Dynamic LINQ 的关键差异**:
// ❌ Dynamic LINQ:字段名拼写错误只在运行时数据库查询时才暴露
var q = db.Users.Where("Ag > 18").ToList(); // Ag 拼错 → SQL 报错,延迟到运行时
// ✅ ExpressionVisitor 方案:编译期 / 运行期立即报错
var rules = new[] { new FilterRule("Ag", FilterOp.Gt, 18) };
var expr = FilterBuilder<User>.Build(rules); // ArgumentException: 字段不存在: Ag
───
易错点提醒
• ❌ 字段名写成魔法字符串 → 应用 typeof(T).GetProperty(name) 反射校验
• ❌ 用 Func 而不是 Expression → 翻译不到 SQL,会被 EF 警告并降级到内存过滤(性能灾难)
• ❌ Expression.Property 用 BindingFlags 反射字段会丢字段引用 → 用 typeof(T).GetProperty(name) 拿 PropertyInfo
• ❌ IN 翻译时忘记用 List<object>.Contains 重载 → 会被翻译成 SQL EXISTS 而不是 IN(性能略差)
• ❌ Between 用闭区间 >= + <= → 跟 SQL 标准 BETWEEN(闭区间)冲突;建议 [from, to) 半开区间,跟 C# Range 语义一致
• ❌ 多层嵌套 AND/OR 时括号丢失 → 必须用 Expression.Block 显式控制优先级
---
<!-- message_id: om_x100b68268d2524a0b21c6036af1e15d -->
🚀 **Day 5 · 扩展思考 + 总结**
───
扩展思考(建议动手验证)
1. **IN (1,2,3) 高效翻译**:上面的 BuildIn 用 List<object>.Contains 会被 EF Core 翻译成 SQL EXISTS 子查询。如何改写让它翻译成原生 IN 子句?提示:用 Expression.NewArrayInit + Array.Contains 数组扩展方法。
2. **日期范围 [from, to) 半开区间**:为什么数据库时间字段常用半开区间(>= from AND < to)而不是闭区间?提示:跟 DateTime.Now 的精度(毫秒/微秒)和索引范围扫描有关。
3. **嵌套子查询翻译**:如果过滤条件里包含"用户最近一次登录的会话"(子查询),ExpressionVisitor 如何处理 VisitMethodCall 中的 Queryable.FirstOrDefault?提示:EF Core 用 Select 子查询包装。
4. **IAsyncEnumerable<T> 跟延迟执行的关系**:C# 8+ 的 await foreach 是延迟 + 异步流的结合,跟 IEnumerable<T> 的延迟执行有何异同?底层用 ConfiguredCancelableAsyncEnumerable<T> + 状态机(**关联 Day 4 async/await**)。
5. **表达式树性能 vs 直接 lambda**:在 WPF 实时数据场景下,Expression<Func<T, bool>>.Compile() 的成本是多少?能否缓存编译结果?提示:JIT 编译是冷启动开销,缓存后每次调用 < 10ns。
───
Day 5 总结
| 维度 | 关键收获 |
| ------------------ | ---------------------------------------------------------------- |
| 延迟执行 | 链式调用只构造描述符,MoveNext() 才执行;终结符触发立即执行 |
| Func vs Expression | IL vs 数据结构;后者可翻译、可分析、可修改 |
| ExpressionVisitor | 访问者模式遍历表达式树,5 个核心方法拦截不同节点类型 |
| EF Core 翻译原理 | IQueryProvider + RelationalSqlTranslatingExpressionVisitor + 参数化 |
| Q3 实战 | 强类型字段引用 + FilterBuilder<T> + 自定义 Visitor 是生产级首选 |
───
📌 配套学习资源(建议深读)
• **官方文档**:[System.Linq.Expressions 命名空间](https://learn.microsoft.com/dotnet/api/system.linq.expressions) —— 所有 Expression 节点类型的权威定义
• **EF Core 源码**:[RelationalSqlTranslatingExpressionVisitor.cs](https://github.com/dotnet/efcore/blob/main/src/EFCore.Relational/Query/Internal/RelationalSqlTranslatingExpressionVisitor.cs) —— 真实世界最复杂的 ExpressionVisitor 实现(2000+ 行)
• **进阶书籍**:《C# 12 in a Nutshell》Chapter 8(LINQ) + Chapter 9(LINQ to XML/EF)
• **博客**:Matt Warren 的 ["Building a LINQ Provider"](https://docs.microsoft.com/archive/blogs/mattwar/building-a-linq-provider) 系列(15 年经典教程)——手把手教你写一个迷你 LINQ Provider
───
🎤 等主人回复
主人,Day 5 推送完毕。3 道面试题覆盖了:
• **Q1**:延迟 vs 立即的核心标志(必会)
• **Q2**:表达式树 vs 委托的 IL 层区别 + EF Core 翻译原理(高频面试题)
• **Q3**:WPF MVVM 动态过滤实战(架构设计题,常出现在 P6/P7 面试)
请主人按以下顺序给我反馈:
1. 哪道题最有共鸣 / 哪个点你之前没意识到?
2. 对 3 道题的答案,**你有什么不同看法或补充**?
3. 想让我下次深挖哪个点?(比如:表达式树性能优化 / EF Core 翻译边界 / IAsyncEnumerable<T> 深入)
如果你有手头的项目场景(动态过滤 UI / 自定义 LINQ Provider / 性能优化),可以直接抛给我——我会结合真实场景拆解,而不是停留在教科书层面。
⏰ **下次预告**:Day 6 将教 **反射与元数据**(Phase 1 第 6 个)。重点:Type / Assembly / MethodInfo / Attribute 的运行时模型、Reflection.Emit vs Expression.Compile()、WPF 中 XAML 加载 / DataTemplate 选择器 / IValueConverter 的反射应用、BindingFlags 性能陷阱。
——
严老师 · Day 5 完成 · 等待主人回复