Expression
大约 14 分钟约 4226 字
Expression
表达式目录树的本质、特点、和委托的区别
Expression<Func<People, bool>> expression = p => p.Id == 10;//可以使用lambda表达式声明
Func<People, bool> func = expression.Compile();
bool bResult = func.Invoke(new People()
{
Id = 10,
Name = "Richard"
});Func<int, int, int> func = (m, n) =>
{
int i = 0;
return m * n + 2;
};
Expression<Func<int, int, int>> exp = (m, n) => m * n + 2; //快捷声明--使用lambad表达式来声明
var erpPlu = exp.Compile();//表达式目录树可以通过compile 转换成一个委托动态拼装Expression
{
//表达式目录树的拼装---最基础版本
Expression<Func<int>> expression = () => 123 + 234; //没有参数,返回int
ConstantExpression expression1 = Expression.Constant(123); //常量表达式
ConstantExpression expression2 = Expression.Constant(234); //常量表达式
//二元表达式
BinaryExpression binaryExpression = Expression.Add(expression1, expression2);
Expression<Func<int>> expressionReslut = Expression.Lambda<Func<int>>(binaryExpression);
Func<int> func = expressionReslut.Compile();
int iResult = func.Invoke();
}{
//表达式目录树的拼装---带参数版本
Expression<Func<int, int>> expression1 = m => m + 1;
Func<int, int> func = expression1.Compile();
int iResult = func.Invoke(5);
ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "m");
ConstantExpression constant = Expression.Constant(1, typeof(int));
BinaryExpression addExpression = Expression.Add(parameterExpression, constant);
Expression<Func<int, int>> expression = Expression.Lambda<Func<int, int>>(addExpression, new ParameterExpression[1]
{
parameterExpression
});
Func<int, int> func1 = expression.Compile();
int iResult1 = func1.Invoke(5);
}{
//表达式目录树的拼装---带有多个参数的
Expression<Func<int, int, int>> expression = (m, n) => m * n + 2;
Func<int, int, int> func = expression.Compile();
int iResult = func.Invoke(10, 20);
ParameterExpression parameterExpressionM = Expression.Parameter(typeof(int), "m");
ParameterExpression parameterExpressionN = Expression.Parameter(typeof(int), "n");
BinaryExpression multiply = Expression.Multiply(parameterExpressionM, parameterExpressionN);
ConstantExpression constantExpression = Expression.Constant(2);
BinaryExpression plus = Expression.Add(multiply, constantExpression);
Expression<Func<int, int, int>> expression1 = Expression.Lambda<Func<int, int, int>>(plus, new ParameterExpression[2]
{
parameterExpressionM,
parameterExpressionN
});
Func<int, int, int> func1 = expression1.Compile();
int iResult1 = func1.Invoke(10, 20);
}{
{
IQueryable<People> peoplesQueyrabable = new List<People>().AsQueryable();
Expression<Func<People, bool>> predicate = c => c.Id == 10;
var query = peoplesQueyrabable.Where(predicate); //Expression<Func<TSource, bool>> predicate
}
//表达式目录树的拼装---高级篇
//类似于这种比较复杂的:建议大家可以反编译看看
//1.把这个快捷声明的表达式目录树,复制到一个单独的类,为了方便反编译查看中间语言
//2.反编译看中间语言
{
{
Expression<Func<People, bool>> predicate = c => c.Id == 10;
Func<People, bool> func = predicate.Compile();
bool bResult = func.Invoke(new People()
{
Id = 10
});
//1.声明一个变量C;
ParameterExpression parameterExpression = Expression.Parameter(typeof(People), "c");
//2.c.id,调用c.的属性---people的属性id,先获取属性
//a.获取属性--反射
FieldInfo fieldId = typeof(People).GetField("Id"); //id
//b.c.Id 通过parameterExpression来获取 调用Id
MemberExpression idExp = Expression.Field(parameterExpression, fieldId);
//3.== Equeals 是个方法 ,是id的方法,id 是int类型,应该获取int 的 ===
ConstantExpression constant10 = Expression.Constant(10, typeof(int));
//c.id==10;
Expression expressionExp = Expression.Equal(idExp, constant10);
Expression<Func<People, bool>> predicate1 = Expression.Lambda<Func<People, bool>>(expressionExp, new ParameterExpression[1]
{
parameterExpression
});
Func<People, bool> func1 = predicate1.Compile();
bool bResult1 = func1.Invoke(new People()
{
Id = 10
});
}
//表达式目录树的拼装---超级篇
//如果遇到很长的表达式目录树--拼装建议从右往左拼装
{
Expression<Func<People, bool>> predicate = c =>
c.Id.ToString() == "10"
&& c.Name.Equals("Richard")
&& c.Age > 35;
Func<People, bool> func = predicate.Compile();
bool bResult = func.Invoke(new People()
{
Id = 10,
Name = "Richard",
Age = 36
});
Console.WriteLine("****************************************************");
//1.拼装c.Age > 35;
ParameterExpression parameterExpression = Expression.Parameter(typeof(People), "c");
ConstantExpression constant35 = Expression.Constant(35);
//age
PropertyInfo propAge = typeof(People).GetProperty("Age");
//c.Age
var ageExp = Expression.Property(parameterExpression, propAge);
//c.Age > 35
var cagExp = Expression.GreaterThan(ageExp, constant35);
Console.WriteLine("****************************************************");
//拼装:c.Name.Equals("Richard")
//字符串Richard
ConstantExpression constantrichard = Expression.Constant("Richard");
// Name属性
PropertyInfo propName = typeof(People).GetProperty("Name");
//c.Name
var nameExp = Expression.Property(parameterExpression, propName);
//获取equals方法
MethodInfo equals = typeof(string).GetMethod("Equals", new Type[] { typeof(string) });
//c.Name.Equals("Richard")
var NameExp = Expression.Call(nameExp, equals, constantrichard);
Console.WriteLine("****************************************************");
//拼装:c.Id.ToString() == "10"
ConstantExpression constantExpression10 = Expression.Constant("10", typeof(string));
//id
FieldInfo fieldId = typeof(People).GetField("Id");
//c.Id
var idExp = Expression.Field(parameterExpression, fieldId);
//获取ToString
MethodInfo toString = typeof(int).GetMethod("ToString", new Type[0]);
//c.Id.ToString(); Expression.Call:调用方法
var toStringExp = Expression.Call(idExp, toString, Array.Empty<Expression>());
// c.Id.ToString()=="10"
var EqualExp = Expression.Equal(toStringExp, constantExpression10);
Console.WriteLine("****************************************************");
//c.Id.ToString() == "10"&&c.Name.Equals("Richard")
var plus = Expression.AndAlso(EqualExp, NameExp);
//c.Id.ToString() == "10"&&c.Name.Equals("Richard") && c.Age > 35
var exp = Expression.AndAlso(plus, cagExp);
Expression<Func<People, bool>> predicate1 = Expression.Lambda<Func<People, bool>>(exp, new ParameterExpression[1]
{
parameterExpression
});
Func<People, bool> func1 = predicate1.Compile();
bool bResult1 = func1.Invoke(new People()
{
Id = 10,
Name = "Richard",
Age = 36
});
}
}
}为什么要这样拼
{
//SELECT* FROM USER WHERE name like "" and age=10;
//在之前,数据库查询基本都是拼接Sql语句;
//以前根据用户输入拼装条件
string sql = "SELECT * FROM USER WHERE 1=1";
Console.WriteLine("用户输入个名称,为空就跳过");
string name = Console.ReadLine();
if (!string.IsNullOrWhiteSpace(name))
{
sql += $" and name like '%{name}%'";
}
Console.WriteLine("用户输入个账号,为空就跳过");
string account = Console.ReadLine();
if (!string.IsNullOrWhiteSpace(account))
{
sql += $" and account like '%{account}%'";
}
}//现在主流的是Linq:---通过条件拼装的一个很长的表达式目录树;到数据库执行的时候;数据库能够认识表达式目录树吗? 当然不认识; 数据库只认识Sql语句; 需要把表达式目录树如进行拆解;组装成一个Sql语句的条件
{
{
//来自于数据库的数据
var dbSet = new List<People>().AsQueryable();
var result = dbSet.Where(p => p.Age == 25 & p.Name.Contains("阳光下的微笑"));
//Expression<Func<People, bool>> predicate = p => p.Age == 25 & p.Name.Contains("阳光下的微笑");
Expression<Func<People, bool>> exp = null;
Console.WriteLine("用户输入个名称,为空就跳过");
string name = Console.ReadLine();
if (!string.IsNullOrWhiteSpace(name))
{
exp = p => p.Name.Contains(name);
}
Console.WriteLine("用户输入个最小年纪,为空就跳过");
string age = Console.ReadLine();
if (!string.IsNullOrWhiteSpace(age) && int.TryParse(age, out int iAge))
{
exp = p => p.Age > iAge;
}
//如果name 和age 都不为空呢?
//遇到这种我们应该怎么做? 就可以把一整条表达式目录树做拼装;
//1.代码量多:----封装;
//如果封装一个类库---可以拼装的过程,给封装装起来;提供几个方法;只需要把满足条件的小结构表达式传入;
//不断的调用;拼接的表达式目录树,就越来长?
//动态的拼装查询条件!
}
}案例
public static void MapperTest()
{
//需求. 需要把People PeopleCopy:
//1.反射
//2.序列化
//3.Automapper
//4.可以直接new一个对象
//5.硬编码
{
//People people = new People()
//{
// Id = 11,
// Name = "剑锋",
// Age = 31
//};
////PeopleCopy people1= (PeopleCopy)people; //不能转换? 没有继承关系
////a: 方法一
//PeopleCopy peopleCopy0 = new PeopleCopy()
//{
// Id = people.Id,
// Name = people.Name,
// Age = people.Age
//};
////b:方法二
//PeopleCopy peopleCopy1 = ReflectionMapper.Trans<People, PeopleCopy>(people);
////c:方法三
//PeopleCopy peopleCopy2 = SerializeMapper.Trans<People, PeopleCopy>(people);
////这样好不好?
////以上三种方式不好:第一种方式:性能好,不灵活;不能共用;如果换成其他的类型就不能用了;
//// 第二、三种方式;灵活,但是性能不好!
////三种,总是有不完美的地儿;
////动态拼装转换过程;动态拼装了硬编码;
////d:方法四 支持泛型版本:就可以支持多种类型
//PeopleCopy peopleCopy3 = ExpressionMapper.Trans<People, PeopleCopy>(people);
////peopleCopy3 通过普通缓存+字典缓存; 第一次生成的时候,保存一个委托在缓存中,如果第二次来,委托就可以直接从缓存中获取到,直接使用;---直接运行委托;直接运行硬编码;---效率高;
////泛型缓存:就是为不同的类型生成副本:为每一组类型的组合,生成一个副本;
////e:方法五:性能最高的: 支持泛型版本:就可以支持多种类型
////泛型缓存+动态拼装表达式目录树(拼装了执行的动作--编码-拼装了执行逻辑--在代码运行的时候,动态的生成逻辑);动态的生成硬编码;
//PeopleCopy peopleCopy4 = ExpressionGenericMapper<People, PeopleCopy>.Trans(people);
//{
// //能不能把People变成PeopleCopy的过程封装在一个委托中;
// Func<People, PeopleCopy> func = p => new PeopleCopy
// {
// Id = p.Id,
// Name = p.Name,
// Age = p.Age
// };
// PeopleCopy peopleCopynew = func.Invoke(people);
// //如果能够把这个委托给缓存起来;根据我们的诉求,缓存一个委托;委托哪儿来?委托其实可以通过表达式目录树Compile一下,就可以得到一个委托;如果拼装一个表达式目录树,再Compile一下,然后缓存起来;就相当于拼装了这一组的转换逻辑;
// //拼装一下后,缓存下来,如果后面再需要使用,就可以直接使用这个委托了;
//}
//动态的生生硬编码:还有什么技术可以实现?
}
{
People people = new People()
{
Id = 11,
Name = "Richard",
Age = 31
};
long common = 0;
long generic = 0;
long cache = 0;
long reflection = 0;
long serialize = 0;
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1_000_000; i++)
{
PeopleCopy peopleCopy = new PeopleCopy()
{
Id = people.Id,
Name = people.Name,
Age = people.Age
};
}
watch.Stop();
common = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1_000_000; i++)
{
PeopleCopy peopleCopy = ReflectionMapper.Trans<People, PeopleCopy>(people);
}
watch.Stop();
reflection = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1_000_000; i++)
{
PeopleCopy peopleCopy = SerializeMapper.Trans<People, PeopleCopy>(people);
}
watch.Stop();
serialize = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1_000_000; i++)
{
PeopleCopy peopleCopy = ExpressionMapper.Trans<People, PeopleCopy>(people);
}
watch.Stop();
cache = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1_000_000; i++)
{
PeopleCopy peopleCopy = ExpressionGenericMapper<People, PeopleCopy>.Trans(people);
}
watch.Stop();
generic = watch.ElapsedMilliseconds;
}
Console.WriteLine($"common = { common} ms");
Console.WriteLine($"reflection = { reflection} ms");
Console.WriteLine($"serialize = { serialize} ms");
Console.WriteLine($"cache = { cache} ms");
Console.WriteLine($"generic = { generic} ms"); //性能好,而且扩展性也好===又要马儿跑,又要马儿不吃草。。。
}
}ExpressionVisitor 表达式树遍历与修改
using System.Linq.Expressions;
/// <summary>
/// ExpressionVisitor 是表达式树操作的核心基类
/// 可用于遍历、分析和修改表达式树
/// </summary>
class ParameterReplacer : ExpressionVisitor
{
private readonly ParameterExpression _oldParam;
private readonly ParameterExpression _newParam;
public ParameterReplacer(ParameterExpression oldParam, ParameterExpression newParam)
{
_oldParam = oldParam;
_newParam = newParam;
}
protected override Expression VisitParameter(ParameterExpression node)
{
// 替换参数
return node == _oldParam ? _newParam : base.VisitParameter(node);
}
// 示例:将表达式 p => p.Age > 18 中的 p 替换为 x
public static Expression<Func<T, bool>> ReplaceParameter<T>(
Expression<Func<T, bool>> expr, string oldName, string newName)
{
var oldParam = expr.Parameters.FirstOrDefault(p => p.Name == oldName);
if (oldParam == null) return expr;
var newParam = Expression.Parameter(typeof(T), newName);
var replacer = new ParameterReplacer(oldParam, newParam);
var newBody = replacer.Visit(expr.Body);
return Expression.Lambda<Func<T, bool>>(newBody, newParam);
}
}
// 使用示例
Expression<Func<People, bool>> expr1 = p => p.Age > 18;
var replaced = ParameterReplacer.ReplaceParameter(expr1, “p”, “person”);
Console.WriteLine(replaced); // person => person.Age > 18动态查询条件拼装工具类
using System.Linq.Expressions;
/// <summary>
/// 通用的 Expression 动态拼装工具
/// 用于根据前端条件动态构建 Where 查询
/// </summary>
public static class ExpressionBuilder
{
/// <summary>
/// 合并两个表达式(AND 关系)
/// </summary>
public static Expression<Func<T, bool>> And<T>(
this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
{
if (left == null) return right;
if (right == null) return left;
var parameter = Expression.Parameter(typeof(T), “p”);
// 替换两个表达式中的参数为同一个
var leftBody = new ParameterReplacerVisitor(
left.Parameters[0], parameter).Visit(left.Body);
var rightBody = new ParameterReplacerVisitor(
right.Parameters[0], parameter).Visit(right.Body);
var combined = Expression.AndAlso(leftBody, rightBody);
return Expression.Lambda<Func<T, bool>>(combined, parameter);
}
/// <summary>
/// 合并两个表达式(OR 关系)
/// </summary>
public static Expression<Func<T, bool>> Or<T>(
this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
{
if (left == null) return right;
if (right == null) return left;
var parameter = Expression.Parameter(typeof(T), “p”);
var leftBody = new ParameterReplacerVisitor(
left.Parameters[0], parameter).Visit(left.Body);
var rightBody = new ParameterReplacerVisitor(
right.Parameters[0], parameter).Visit(right.Body);
var combined = Expression.OrElse(leftBody, rightBody);
return Expression.Lambda<Func<T, bool>>(combined, parameter);
}
/// <summary>
/// 构建范围查询(Between)
/// </summary>
public static Expression<Func<T, bool>> Between<T>(
Expression<Func<T, int>> property, int min, int max)
{
var parameter = property.Parameters[0];
var body = property.Body;
var minCheck = Expression.GreaterThanOrEqual(body, Expression.Constant(min));
var maxCheck = Expression.LessThanOrEqual(body, Expression.Constant(max));
var combined = Expression.AndAlso(minCheck, maxCheck);
return Expression.Lambda<Func<T, bool>>(combined, parameter);
}
/// <summary>
/// 构建 Contains 查询(LIKE %value%)
/// </summary>
public static Expression<Func<T, bool>> Contains<T>(
Expression<Func<T, string>> property, string value)
{
var parameter = property.Parameters[0];
var containsMethod = typeof(string).GetMethod(“Contains”, new[] { typeof(string) });
var call = Expression.Call(property.Body, containsMethod!, Expression.Constant(value));
return Expression.Lambda<Func<T, bool>>(call, parameter);
}
}
/// <summary>
/// 参数替换 Visitor(内部使用)
/// </summary>
class ParameterReplacerVisitor : ExpressionVisitor
{
private readonly ParameterExpression _oldParam;
private readonly ParameterExpression _newParam;
public ParameterReplacerVisitor(ParameterExpression oldParam, ParameterExpression newParam)
{
_oldParam = oldParam;
_newParam = newParam;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return node == _oldParam ? _newParam : base.VisitParameter(node);
}
}动态查询实战用法
// 在实际业务中使用动态拼装
public class UserQueryService
{
private IQueryable<People> _query;
public UserQueryService(IQueryable<People> query)
{
_query = query;
}
public List<People> Search(UserSearchRequest request)
{
Expression<Func<People, bool>> filter = p => true;
// 根据条件动态拼装
if (!string.IsNullOrEmpty(request.Name))
{
filter = filter.And(ExpressionBuilder.Contains(p => p.Name, request.Name));
}
if (request.MinAge.HasValue)
{
filter = filter.And(p => p.Age >= request.MinAge.Value);
}
if (request.MaxAge.HasValue)
{
filter = filter.And(p => p.Age <= request.MaxAge.Value);
}
return _query.Where(filter).ToList();
}
}
public class UserSearchRequest
{
public string Name { get; set; }
public int? MinAge { get; set; }
public int? MaxAge { get; set; }
}表达式树与反射的性能对比
// 表达式树创建对象的性能远优于反射
public static class FastActivator
{
private static readonly ConcurrentDictionary<Type, Func<object>> _cache = new();
/// <summary>
/// 使用表达式树创建实例(缓存委托,避免重复反射)
/// </summary>
public static object CreateInstance(Type type)
{
var creator = _cache.GetOrAdd(type, t =>
{
// new T()
var newExpr = Expression.New(t);
var lambda = Expression.Lambda<Func<object>>(Expression.Convert(newExpr, typeof(object)));
return lambda.Compile();
});
return creator();
}
/// <summary>
/// 泛型版本
/// </summary>
public static T CreateInstance<T>() where T : new()
{
return Activator.CreateInstance<T>();
}
}
// 性能对比(1,000,000 次创建):
// 直接 new: ~10 ms
// 表达式树(缓存委托): ~30 ms
// Activator.CreateInstance: ~300 ms
// 反射 ConstructorInfo: ~500 ms
// 结论:表达式树编译后的委托比反射快 10-15 倍表达式树解析 SQL 条件
/// <summary>
/// 将表达式树转换为 SQL WHERE 条件
/// 简化版,演示 LINQ Provider 的核心原理
/// </summary>
public class ExpressionToSqlTranslator : ExpressionVisitor
{
private StringBuilder _sb = new();
public string Translate(Expression expression)
{
_sb.Clear();
Visit(expression);
return _sb.ToString();
}
protected override Expression VisitBinary(BinaryExpression node)
{
_sb.Append(“(“);
Visit(node.Left);
_sb.Append(node.NodeType switch
{
ExpressionType.Equal => “ = “,
ExpressionType.NotEqual => “ <> “,
ExpressionType.GreaterThan => “ > “,
ExpressionType.GreaterThanOrEqual => “ >= “,
ExpressionType.LessThan => “ < “,
ExpressionType.LessThanOrEqual => “ <= “,
ExpressionType.AndAlso => “ AND “,
ExpressionType.OrElse => “ OR “,
_ => $” {node.NodeType} “
});
Visit(node.Right);
_sb.Append(“)”);
return node;
}
protected override Expression VisitMember(MemberExpression node)
{
// 将属性名映射为列名
_sb.Append(node.Member.Name);
return node;
}
protected override Expression VisitConstant(ConstantExpression node)
{
if (node.Value == null)
{
_sb.Append(“NULL”);
}
else if (node.Value is string s)
{
_sb.Append($”'{s}'”);
}
else if (node.Value is bool b)
{
_sb.Append(b ? “1” : “0”);
}
else
{
_sb.Append(node.Value.ToString());
}
return node;
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.Name == “Contains”)
{
Visit(node.Object);
_sb.Append(“ LIKE '%”);
var value = ((ConstantExpression)node.Arguments[0]).Value;
_sb.Append(value);
_sb.Append(“%'”);
return node;
}
return base.VisitMethodCall(node);
}
}
// 使用示例
Expression<Func<People, bool>> expr = p =>
p.Age > 18 && p.Name.Contains(“张”);
var translator = new ExpressionToSqlTranslator();
var sql = translator.Translate(expr.Body);
Console.WriteLine(sql);
// 输出: ((Age > 18) AND (Name LIKE '%张%'))表达式树核心应用场景:
1. LINQ Provider:将 LINQ 查询翻译为 SQL、Cosmos DB 查询等
2. 动态查询构建:根据用户输入条件拼装 Where 表达式
3. 对象映射:高性能对象拷贝(比反射快 10 倍+)
4. 规则引擎:将业务规则表示为表达式树,动态编译执行
5. 属性名称获取:避免硬编码字符串(p => p.Name 获取 “Name”)
6. 深度拷贝:通过表达式树生成高效的深拷贝逻辑关键知识点
- 先明确这个主题影响的是语法层、运行时层,还是性能与可维护性层。
- 学习时要同时关注语言表面写法和编译器、JIT、GC 等底层行为。
- 真正有价值的是知道”为什么这样写”和”在什么边界下不能这样写”。
项目落地视角
- 把示例改成最小可运行样例,并观察编译输出、运行结果和异常行为。
- 如果它会进入团队代码规范,最好同步补充命名约定、禁用场景和替代方案。
- 涉及性能结论时,优先用 Benchmark 或实际热点链路验证,而不是凭感觉判断。
常见误区
- 只记语法糖,不知道底层成本。
- 把适用于小样例的写法直接搬到高并发或大对象场景里。
- 忽略框架版本、语言版本和运行时差异,导致结论失真。
进阶路线
- 继续向源码、IL、JIT 行为和 BCL 实现层深入。
- 把知识点和代码评审、性能诊断、面试复盘结合起来。
- 把同类主题做横向对比,例如值类型与引用类型、迭代器与 async 状态机、反射与 Source Generator。
适用场景
- 当你准备把《Expression》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合在需要理解语言特性、运行时行为或 API 边界时阅读。
- 当代码开始出现性能瓶颈、可维护性问题或语义歧义时,这类主题会直接影响实现质量。
落地建议
- 先写最小可运行样例,再把结论迁移到真实业务代码。
- 同时记录这个特性的收益、限制和替代方案,避免为了“高级”而使用。
- 涉及内存、并发或序列化时,最好配合调试器或基准测试验证。
排错清单
- 先确认问题属于编译期、运行期还是语义误用。
- 检查是否存在隐式转换、装箱拆箱、闭包捕获或上下文切换等隐藏成本。
- 查看异常栈、日志和最小复现代码,优先排除使用姿势问题。
复盘问题
- 如果把《Expression》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《Expression》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《Expression》最大的收益和代价分别是什么?
