目录
一、定义
表达式目录树是一种语法树,是一种数据结构
二、与委托的区别
1、在扩展方法表达式中的区别
List<Person> persons = new Person().Query();
persons.Where(p => p.Id == 1); // where扩展方法传入的是一个委托
persons.AsQueryable().Where(p => p.Id == 1); //where 扩展方法传入的是一个Expression(表达式目录树)
- IEnumerable类型的扩展方法的参数是委托
- IQueryable类型扩展方法的参数是表达式目录树(数据结构)
2、声明方式的区别
- 委托的方法体可以有多行
- 表达式目录树的方法体只能有一行,且不能用大括号
public static void Show()
{
Func<int, int, int> func = (m, n) => 2 * m + n;
Func<int, int, int> func1 = (m, n) =>
{
Console.WriteLine("委托的方法体可以有多行");
return 2 * m + n;
};
Expression<Func<int, int, int>> expression = (m, n) => 2 * m + n;
//Expression<Func<int, int, int>> expression1 = (m, n) =>
//{
// Console.WriteLine("表达式目录树的方法体只能有一行,且不能用大括号");
// return 2 * m + n;
//}
int funcResult = func.Invoke(1, 2);
Console.WriteLine(funcResult);
int expResult = expression.Compile().Invoke(1, 2);
Console.WriteLine(expResult);
}
func本质就是一个方法
expression本质就是一个数据结构
3、执行的区别
- 执行委托:直接Invoke;
- 执行表达式目录树:
expression.Compile().Invoke(1, 2);
先调用Compile()
转换成委托,在执行Invoke。
int funcResult = func.Invoke(1, 2);
int funcResult1 = func(1, 2);
Console.WriteLine(funcResult);
int expResult = expression.Compile().Invoke(1, 2);
int expResult1 = expression.Compile()(1, 2);
Console.WriteLine(expResult);
三、表达式目录树的本质
表达式目录树的本质是一种数据结构------->二叉树
(1)表达式 2*m+n
可以进行拆解,可以分为左边和右边,左边为2*m
,右边为n
(每一步拆解都是先把右边拆成最小单元)
(2)左边的2*m
还能拆分为左边和右边,左边为2
,右边为m
因此,表达式目录树本质就是二叉树。
四、表达式目录树的拆分/拼接
使用lambda的方式属于快捷声明方式
1、常量表达式目录树
//常量表达式目录树
Expression<Func<int>> exConst = () => 123 + 234;
那么不用快捷方式应该怎么拼接呢?以常量表达式目录树为例:先看下编译后的结果
那么终极目标就是拼接成上面的结果就可以了,因为是常量相加,所以编译时已经做了优化,直接算出结果
Expression expLift = Expression.Constant(123); //左边
Expression expRight = Expression.Constant(234); //右边
Expression expSum = Expression.Add(expLift, expRight);
Expression<Func<int>> expFun = Expression.Lambda<Func<int>>(expSum);
int result = expFun.Compile().Invoke();
2、复杂的表达式目录树(简单参数)
//快捷声明
Expression<Func<int, int, int>> exp = (m, n) => m * n + m + n + 2;
同样看一下反编译结果,注意要将反编译工具的C#版本选择到1.0,或者2.0才能看到反编译结果
拆分拼接(从右往左拆)
//参数
ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "m");
ParameterExpression parameterExpression2 = Expression.Parameter(typeof(int), "n");
//常量2
Expression expConst = Expression.Constant(2, typeof(int));
//m*n
Expression expMultiply = Expression.Multiply(parameterExpression, parameterExpression2);
//m*n+m
Expression expSum = Expression.Add(expMultiply, parameterExpression);
// m*n+m+n
Expression expSum1 = Expression.Add(expSum, parameterExpression2);
//m*n+m+n+2
Expression expSum2 = Expression.Add(expSum1, expConst);
//转换成lambda
Expression<Func<int, int, int>> exp1 = Expression.Lambda<Func<int, int, int>>(expSum2, new ParameterExpression[2]
{
parameterExpression,
parameterExpression2
});
int iResult = exp1.Compile().Invoke(10, 11);
拆分过程图
对应关系
3、复杂表达式目录树(复杂参数)
//复杂表达式目录树:参数含有对象
Expression<Func<Person, bool>> lambdaExp = x => x.Id == 1;
先反编译看结果:
拆分拼接(从右往左拆)
//拆分
ParameterExpression parameterExpression = Expression.Parameter(typeof(Person), "x");
//常量"1"
Expression expConst = Expression.Constant("1", typeof(string));
//获取Id属性
PropertyInfo idProp = typeof(Person).GetProperty("Id");
//获取到x.Id
Expression expId = Expression.Property(parameterExpression, idProp);
//获取int类型的ToString方法(获取到无参的方法)
MethodInfo toString = typeof(int).GetMethod("ToString", new Type[0]);
//获取x.Id.ToString()
Expression expToString = Expression.Call(expId, toString, Array.Empty<Expression>());
//获取到Equals方法
MethodInfo equals = typeof(string).GetMethod("Equals", new Type[] { typeof(string) });
//获取x.Id.ToString()Equals("1")
Expression expEquals = Expression.Call(expToString, equals, expConst);
//最终替换结果
Expression<Func<Person, bool>> lambdaExp1 = Expression.Lambda<Func<Person, bool>>(expEquals, new ParameterExpression[1]
{
parameterExpression
});
Func<Person, bool> func = lambdaExp1.Compile();
bool result = func.Invoke(new Person { Id = 1, Name = "张三" });
拆解过程
(1) 获取常量“1”
(2) 获取id属性和x.Id
(3) 获取int类型的ToString()方法
(4) 获取Equals方法
五、表达式目录树的应用
需求:有两个对象Person和People,字段属性类型都相同,需要将Person转换成People
思路:大致有五种方法
- 硬编码:直接给每个属性赋值 (性能最高)
- 反射
- 序列化和反序列化 (性能最差)
- 表达式目录树字典缓存
- 表达式目录树泛型缓存(性能与硬编码接近)
1、硬编码
public People(Person person)
{
Id = person.Id;
Name = person.Name;
}
2、反射
class ReflectionMapper
{
/// <summary>
/// 反射转换对象
/// </summary>
/// <typeparam name="TSource">源</typeparam>
/// <typeparam name="TTarget">目标</typeparam>
/// <param name="source"></param>
/// <returns></returns>
public static TTarget Mapper<TSource, TTarget>(TSource source)
{
TTarget target = Activator.CreateInstance<TTarget>();
PropertyInfo[] targetPropertyInfos = target.GetType().GetProperties();
Type sourceType = source.GetType();
foreach (var targetProp in targetPropertyInfos)
{
PropertyInfo sourceProp = sourceType.GetProperty(targetProp.Name);
targetProp.SetValue(target, sourceProp.GetValue(source));
}
FieldInfo[] targetFieldInfos = target.GetType().GetFields();
foreach (var targetField in targetFieldInfos)
{
FieldInfo sourceField = sourceType.GetField(targetField.Name);
targetField.SetValue(target, sourceField.GetValue(source));
}
return target;
}
}
3、序列化和反序列化
- System.Text.Json序列化和反序列化转换对象(性能略优)
- Newtonsoft.Json序列化和反序列化转换对象
class SerializeMapper
{
/// <summary>
/// System.Text.Json序列化和反序列化转换对象
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TTarget"></typeparam>
/// <param name="source"></param>
/// <returns></returns>
public static TTarget Mapper<TSource, TTarget>(TSource source)
{
return System.Text.Json.JsonSerializer.Deserialize<TTarget>(System.Text.Json.JsonSerializer.Serialize(source));
}
/// <summary>
/// Newtonsoft.Json序列化和反序列化转换对象
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TTarget"></typeparam>
/// <param name="source"></param>
/// <returns></returns>
public static TTarget NewtonMapper<TSource, TTarget>(TSource source)
{
return JsonConvert.DeserializeObject<TTarget>(JsonConvert.SerializeObject(source));
}
}
4、表达式目录树字典缓存
class ExpressionDicMapper
{
/// <summary>
/// 字典缓存--hash分布
/// </summary>
private static Dictionary<string, object> _Dic = new Dictionary<string, object>();
/// <summary>
/// 字典缓存目录树
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TTarget"></typeparam>
/// <param name="source"></param>
/// <returns></returns>
public static TTarget Mapper<TSource, TTarget>(TSource source)
{
string key = $"funckey_{typeof(TSource).FullName}_{typeof(TTarget).FullName}";
if (!_Dic.ContainsKey(key))
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(TSource), "p");
List<MemberBinding> memberBindingList = new List<MemberBinding>();
PropertyInfo[] targetPropertyInfos = typeof(TTarget).GetProperties();
foreach (var targetProp in targetPropertyInfos)
{
//获取属性
Expression property = Expression.Property(parameterExpression, typeof(TSource).GetProperty(targetProp.Name));
MemberBinding memberBinding = Expression.Bind(targetProp, property);
memberBindingList.Add(memberBinding);
}
FieldInfo[] targetFieldInfos = typeof(TTarget).GetFields();
foreach (var targetField in targetFieldInfos)
{
//获取属性
Expression field = Expression.Field(parameterExpression, typeof(TSource).GetField(targetField.Name));
MemberBinding memberBinding = Expression.Bind(targetField, field);
memberBindingList.Add(memberBinding);
}
MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TTarget)), memberBindingList.ToArray());
Expression<Func<TSource, TTarget>> exp = Expression.Lambda<Func<TSource, TTarget>>(memberInitExpression, new ParameterExpression[1]
{
parameterExpression
});
//拼装是一次性的。如果字典中包含了,就不会再拼装了,实现了缓存
Func<TSource, TTarget> func = exp.Compile();
_Dic[key] = func;
}
return ((Func<TSource, TTarget>)_Dic[key]).Invoke(source);
}
}
5、表达式目录树泛型缓存
class ExpressionGenericMapper<TSource, TTarget>
{
/// <summary>
/// 泛型委托缓存
/// </summary>
private static Func<TSource, TTarget> _Func = null;
/// <summary>
/// 静态化构造函数实现仅拼装一次表达式目录树
/// </summary>
static ExpressionGenericMapper()
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(TSource), "p");
List<MemberBinding> memberBindingList = new List<MemberBinding>();
PropertyInfo[] targetPropertyInfos = typeof(TTarget).GetProperties();
foreach (var targetProp in targetPropertyInfos)
{
//获取属性
Expression property = Expression.Property(parameterExpression, typeof(TSource).GetProperty(targetProp.Name));
MemberBinding memberBinding = Expression.Bind(targetProp, property);
memberBindingList.Add(memberBinding);
}
FieldInfo[] targetFieldInfos = typeof(TTarget).GetFields();
foreach (var targetField in targetFieldInfos)
{
//获取属性
Expression field = Expression.Field(parameterExpression, typeof(TSource).GetField(targetField.Name));
MemberBinding memberBinding = Expression.Bind(targetField, field);
memberBindingList.Add(memberBinding);
}
MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TTarget)), memberBindingList.ToArray());
Expression<Func<TSource, TTarget>> exp = Expression.Lambda<Func<TSource, TTarget>>(memberInitExpression, new ParameterExpression[1]
{
parameterExpression
});
//拼装是一次性的,如果泛型委托中包含了,就不会再拼装了,实现了缓存
_Func = exp.Compile();
}
/// <summary>
/// 对外方法
/// </summary>
/// <param name="source">源</param>
/// <returns></returns>
public static TTarget Mapper(TSource source)
{
return _Func.Invoke(source);
}
}
测试代码
public static void ExpressMapper()
{
Person person = new Person
{
Id = 1,
Name = "张三"
};
{
/*
* 1、硬编码:直接给每个属性赋值
*/
Stopwatch watch = new Stopwatch();
watch.Start();
//循环一百万次
for (int i = 0; i < 1_000_000; i++)
{
People people = new People(person);
}
watch.Stop();
Console.WriteLine($"硬编码耗时:{watch.ElapsedMilliseconds} ms");
}
{
/*
* 2、反射
*/
Stopwatch watch = new Stopwatch();
watch.Start();
//循环一百万次
for (int i = 0; i < 1_000_000; i++)
{
People people = ReflectionMapper.Mapper<Person, People>(person);
}
watch.Stop();
Console.WriteLine($"反射耗时:{watch.ElapsedMilliseconds} ms");
}
{
/*
* 3.1、System.Text.Json序列化和反序列化转换对象
*/
Stopwatch watch = new Stopwatch();
watch.Start();
//循环一百万次
for (int i = 0; i < 1_000_000; i++)
{
People people = SerializeMapper.Mapper<Person, People>(person);
}
watch.Stop();
Console.WriteLine($"System.Text.Json反序列化和反序列化耗时:{watch.ElapsedMilliseconds} ms");
}
{
/*
* 3.2、Newtonsoft.Json序列化和反序列化转换对象
*/
Stopwatch watch = new Stopwatch();
watch.Start();
//循环一百万次
for (int i = 0; i < 1_000_000; i++)
{
People people = SerializeMapper.NewtonMapper<Person, People>(person);
}
watch.Stop();
Console.WriteLine($"Newtonsoft.Json反序列化和反序列化耗时:{watch.ElapsedMilliseconds} ms");
}
{
/*
* 4、表达式目录树字典缓存
*/
Stopwatch watch = new Stopwatch();
watch.Start();
//循环一百万次
for (int i = 0; i < 1_000_000; i++)
{
People people = ExpressionDicMapper.Mapper<Person, People>(person);
}
watch.Stop();
Console.WriteLine($"表达式目录树字典缓存耗时:{watch.ElapsedMilliseconds} ms");
}
{
/*
* 5、表达式目录树泛型缓存
*/
Stopwatch watch = new Stopwatch();
watch.Start();
//循环一百万次
for (int i = 0; i < 1_000_000; i++)
{
People people = ExpressionGenericMapper<Person, People>.Mapper(person);
}
watch.Stop();
Console.WriteLine($"表达式目录树泛型缓存耗时:{watch.ElapsedMilliseconds} ms");
}
}
执行结果表明:性能排名依次为:硬编码–>泛型缓存–>字典缓存–>反射–>序列化反序列化
本文完整代码:
表达式目录树