- 一、基本概念
- 二.基本使用
- 初识表达式树
- 原生语法使用
- 运算符基本使用
- 面向对象的基本使用
- 三、ExpressionVisitor
- 简介
- 入门体验
- 案例一
- 案例二
- 四、Expression语法糖应用
- 表达式的拼接
- 自定义扩展方法
- 深拷贝
- 类型已知
- 类型未知
提示:以下是本篇文章正文内容,下面案例可供参考 一、基本概念
- 委托是表达式目录树的一个参数类型
- 表达式树是用于描述一个方法的方式,必须调用Combile编译后才能运行
- Expression 编译–Compile–得到一个委托
- Expression只能有一行代码一委托一方法一可以有多行
- 形似于二叉树的一个数据结构
两个功能一摸一样的语法,不同的是,委托可以直接使用,表达式树需要在编译后才能使用
public void test01()
{
//1.委托
Func a = () => "hello world" ;
string str1 = a();
Console.WriteLine(str1);
//2.表达式树
Expression> b = () => "hello world";
//Compile编译后才能使用
string str2 = b.Compile().Invoke();
Console.WriteLine(str2);
}
查看反编译生成的代码,使用c#2.0可以查看到最终编译时生成的语法,比想象中复杂的多
使用原生的语法去编写表达式树,了解底层的操作原理,是如何一步步拼接起来的,对我们后续的应用非常有帮助
1)案例一
无参返回值
public void test02()
{
Func a = () => 5;
//使用Expression工具类去描述我们的方法
//1.先描述参数,参数没有可以不描述
//2.描述方法体
//2.1 此处方法体中只有一个常量()
ConstantExpression con = Expression.Constant(5);
//2.2 方法的结构是lambda,传入方法体的内容,以及lambda的参数
Expression.Lambda>(con,null);
}
2)案例二
有参返回值
public void test03()
{
Func a = (para) => 5+para;
//1.先描述参数,参数的类型|参数名称
ParameterExpression pa = Expression.Parameter(typeof(int), "para");
//2.描述方法体
//2.1 一个常量5
ConstantExpression con = Expression.Constant(5);
//2.2 一个变量,这个变量其实就是参数,不需要再写
//2.3描述加法 5+pa
BinaryExpression ba = Expression.Add(con,pa);
//2.4 描述方法结构 pa=>5+pa
Expression> expression = Expression.Lambda>(ba,new ParameterExpression[] {pa});
int result = expression.Compile().Invoke(3);
Console.WriteLine(result);
}
3)案例三
复杂表达式
public void test04()
{
Func func = (a, b) => a + a * b - 3;
//1.先描述方法参数
ParameterExpression pa1 = Expression.Parameter(typeof(int), "a");
ParameterExpression pa2 = Expression.Parameter(typeof(int), "b");
//2.描述方法体
//2.1 描述常量3
ConstantExpression con = Expression.Constant(3);
//2.2先描述乘法
BinaryExpression mul = Expression.Multiply(pa1, pa2);
//2.3 描述加法 a+(a*b)
BinaryExpression b1 = Expression.Add(pa1,mul);
//2.4 描述加法 (a+a*b)-3
BinaryExpression body = Expression.Subtract(b1,con);
//2.5 描述整体结构
Expression> expression =
Expression.Lambda>(body, new ParameterExpression[] {pa1,pa2});
int v = expression.Compile().Invoke(1,2);
Console.WriteLine(v);
}
面向对象的基本使用
class Person
{
public string name { get; set; }
public int id { get; set; }
}
------------------------------------------------------------------------
public void test05()
{
Func func=p=>p.id.ToString();
//1.描述参数
ParameterExpression pa1 = Expression.Parameter(typeof(Person), "p");
//2.描述方法体内容 p.id
MemberExpression pro = Expression.Property(pa1, "id");
//3.描述调用方法 p.id.ToString();
MethodCallExpression body = Expression.Call(pro, "toString", null);
//4.方法结构的声明
Expression> expression =
Expression.Lambda>(body,new ParameterExpression[] {pa1});
string result = expression.Compile().Invoke(new Person() { id=32});
Console.WriteLine(result);
}
三、ExpressionVisitor
当我们使用Expression的语法糖,去写简单的语法时,实际编译时会生成大量上面尝试写过的代码,这个过程就是底层ExpressionVisitor的解析过程。
简介- 一个逆向工程,来拆解析Expression语法糖的过程,也是可以操作细节,留下来专门给程序员使用的后门
- 系统提供的有抽象类–就是需要我们来扩展一不能直接用
- Visit方法是解析Expreesion入口—判断当前是什么类型的表达目录树,去调用更加专业的解析方法去接续解析~~ 类型多-每个类型都对应一个方法
- 记住一句话,二元操作是核心内容,所有的表达式过程,都会逐步的拆解成一级级的二元操作,也就是VisitBinary方法
继承此类,实现几个其中的方法
public class MyVisitor:ExpressionVisitor
{
///
/// 重写一下解析入口方法
///
///
///
public override Expression Visit(Expression node)
{
Console.WriteLine("进入到重写方法Visit");
return base.Visit(node);
}
///
/// 重写一下二元操作过程
///
///
///
protected override Expression VisitBinary(BinaryExpression node)
{
Console.WriteLine("进入到重写方法VisitBinary");
return base.VisitBinary(node);
}
///
/// 重写一下逻辑运算
///
///
///
protected override Expression VisitConditional(ConditionalExpression node)
{
Console.WriteLine("进入到重写方法VisitConditional");
return base.VisitConditional(node);
}
}
------------------------------------------------------------------
Expression> lam = p => p.name == "xzp" && p.id>23;
MyVisitor myVisitor = new MyVisitor();
myVisitor.Visit(lam);
------------------------------------------------------
结果如下:
进入到重写方法Visit 进入到重写方法Visit 进入到重写方法VisitBinary 进入到重写方法Visit 进入到重写方法VisitBinary 进入到重写方法Visit 进入到重写方法Visit 进入到重写方法Visit 进入到重写方法Visit 进入到重写方法VisitBinary 进入到重写方法Visit 进入到重写方法Visit 进入到重写方法Visit 进入到重写方法Visit
可以看出Visit方法会被多次返回又重新进行分析,递归式的分发到下面的指定方法
案例一如果我们写的表达式为>,在重写的过程中把他换成<,如何实现?
//目标解析的Expression对象 Expression案例二> lam = p => p.name == "xzp" && p.id>23; ------------------------------------------------------------------------- //首先可以定位到,>出现在二元操作上,那我们可以在此方法上做手脚即可 /// /// 重写一下二元操作过程 /// /// ///protected override Expression VisitBinary(BinaryExpression node) { //当前二元表达式的值类型 Type type1 = node.Type; //当前二元表达式的操作符 ExpressionType nodeType = node.NodeType; //当前二元表达式的左边 Expression left = node.Left; //当前二元表达式的右边 Expression right = node.Right; //在此处不能直接改node的属性,因为它是只读的, //直接返回Expression让这个过程重新解析即可 if (nodeType == ExpressionType.GreaterThan) { return Expression.LessThan(left, right); } Console.WriteLine("进入到重写方法VisitBinary"); return base.VisitBinary(node); }
模拟linq to sql的过程,把筛选条件变成sql语句。
先看linq to sql的生成语句
Expression> func = p => p.name == "xzp" && p.id > 12; IQueryable sql = db.student.Where(func); Console.WriteLine(sql); 结果: SELECt [Extent1].[id] AS [id], [Extent1].[classId] AS [classId], [Extent1].[levelId] AS [levelId], [Extent1].[name] AS [name], [Extent1].[age] AS [age], [Extent1].[sex] AS [sex], [Extent1].[classLevel] AS [classLevel], [Extent1].[classNum] AS [classNum] FROM [dbo].[student] AS [Extent1] WHERe ('xzp' = [Extent1].[name]) AND ([Extent1].[id] > 12)
明显换成了SQL语句,此处仅仅将条件筛选换成SQL的模式
//一个扩展方法,方便后面的使用
public static class MyType
{
public static string TypeToSql(this ExpressionType ex)
{
string result = "";
switch (ex)
{
case ExpressionType.And:
case ExpressionType.AndAlso:
result = "and";
break;
case ExpressionType.Equal:
result = "=";
break;
case ExpressionType.GreaterThan:
result = ">";
break;
case ExpressionType.GreaterThanOrEqual:
result = ">=";
break;
case ExpressionType.LessThan:
result = "<";
break;
case ExpressionType.LessThanOrEqual:
result = "<=";
break;
default:
break;
}
return result;
}
}
------------------------------------------------------------------------
public class MyVisitor:ExpressionVisitor
{
public List sql = new List();
///
/// 重写一下二元操作过程,此处非常重要!!!
/// 默认的解析顺序
/// 1.操作符
/// 2.left表达式
/// 3,right表达式
/// 默认的顺序对我们的拼接是非常不利的!!!需要手动的去指定顺序
///
///
///
protected override Expression VisitBinary(BinaryExpression node)
{
//1.手动解析左边
this.Visit(node.Left);
//2.操作符
sql.Add(" "+node.NodeType.TypeToSql()+" ");
//3.解析右边
this.Visit(node.Right);
//4.返回node,此处已经手动指定了解析顺序,不需要调父类VisitBinary
return node;
}
//p => p.name == "xzp" && p.id
protected override Expression VisitMember(MemberExpression node)
{
//把属性名称添加进去,防止和sql关键重合,可以加[]
sql.Add($"[{node.Member.Name}]");
return base.VisitMember(node);
}
protected override Expression VisitConstant(ConstantExpression node)
{
//值用单引号包起来
sql.Add($"'{node.Value.ToString()}'");
return base.VisitConstant(node);
}
public string getSql()
{
return String.Concat(sql);
}
}
四、Expression语法糖应用
此处总结Expression在描述方法时的重要几个步骤:
- 声明参数(没有参数类型可以不声明)
- 声明执行的内容(方法体,即时他只能有一行表达式)
- 声明方法的结构
下面的两个表达式看上去似乎很好拼接
Expression> func1 = p => p.name == "xzp"; Expression > func2 = p => p.id >12;
1)使用Expression的静态方法add,返回返回值类型并不是Expression的泛型类型,而是一个二元操作
Expression> func1 = p => p.name == "xzp"; Expression > func2 = p => p.id >12; BinaryExpression binaryExpression = Expression.And(func1, func2);
2)执行就会报错,别妄想强行转换成Expression类型,然后就可以执行,这是为什么呢?
- 在上面的原生写法中,我们总结了重要的几个步骤,从步骤就能看出问题:
- 拼接后参数是谁?参数一致吗?怎么声明一致?
- 拼接后的是新方法的描述吗?为什么没有看到方法结构描述的内容?
3)总结,Expression方法看似是帮我们做了第二步,描述了方法体,实际上并不准确,我们需要And的是方法体,而不是整个方法的And。正确方式如下
Expression自定义扩展方法> func1 = p => p.name == "xzp"; Expression > func2 = p => p.id > 12; //1.声明参数,两个一样,随便取一个即可,转换为数组是因为最后声明方法结构时需要参数数组 ParameterExpression[] pas = func1.Parameters.ToArray(); //2.取出各自的方法体 Expression body1 = func1.Body; Expression body2 = func2.Body; //3.拼接成一个方法体 BinaryExpression body = Expression.And(body1, body2); //4.声明方法的结构 Expression > expression = Expression.Lambda >(body, pas);
通过上面的例子,我们可以进行自定义扩展方法的拼接,来处理这种同参数的Expression的拼接。
public static class ExpressionTool
{
public static Expression> And(this Expression> ex1,
Expression> ex2)
{
if(ex1!=null && ex2 != null)
{
ParameterExpression[] pas = ex1.Parameters.ToArray();
BinaryExpression body = Expression.And(ex1.Body, ex2.Body);
return Expression.Lambda>(body, pas);
}
return null;
}
public static Expression> Or(this Expression> ex1,
Expression> ex2)
{
if (ex1 != null && ex2 != null)
{
ParameterExpression[] pas = ex1.Parameters.ToArray();
BinaryExpression body = Expression.Or(ex1.Body, ex2.Body);
return Expression.Lambda>(body, pas);
}
return null;
}
public static Expression> Not(this Expression> ex1)
{
if (ex1 != null)
{
ParameterExpression[] pas = ex1.Parameters.ToArray();
UnaryExpression body = Expression.Not(ex1.Body);
return Expression.Lambda>(body, pas);
}
return null;
}
}
----------------------------------------------------------------
//测试
Expression> func1 = p => p.name == "xzp";
Expression> func2 = p => p.id > 12;
Expression> func3 = p => p.id <23;
Expression> func4 = func1.And(func2).Not().And(func3);
深拷贝
1)深拷贝在面向对象中就是通过赋值的方式依次将属性重新的赋值,浅拷贝即引用的赋值,浅拷贝的危害在于拷贝对象成员的属性等的变化会影响到原对象,因为引用同一个对象。
2)在某些应用场景中,两个不同类型的对象之间,属性可能是相同的,这时候可能需要一次属性的赋值,类似于深拷贝的方式。
两个类型如下:
public class Person
{
public string Name { get; set; }
public string userName { get; set; }
public int Id { get; set; }
public int age { get; set; }
}
--------------------------------------------------------------------------
public class PersonA
{
public string Name { get; set; }
public int Id { get; set; }
public int age { get; set; }
}
-------------------------------------------------------------------------
//一个已知的对象
Person person = new Person()
{
age=12,
Id=1201,
Name="xzp",
userName="admin",
};
类型已知
//如果是已知类型可以这么做(直接编码),性能也是最优的,如果数量较大,也是非常快的
PersonA personA = new PersonA()
{
age=person.age,
Name=person.Name,
Id=person.Id,
};
类型未知
如果类型未知,或者想写一个通用得到方法,通常想到反射,但是在数据量大时,性能会比较差,写法如下
public static T deepCopy(V v) where T:class where V : class { Type typev = typeof(V); Type typet = typeof(T); //1.创建T类型的对象并返回 T t = typet.GetConstructor(new Type[] { }).Invoke(new object[] { }) as T; PropertyInfo[] propertyInfos = typev.GetProperties(); //2.遍历V的属性,获取到属性名 foreach (var pro in propertyInfos) { string name = pro.Name; if(typet.GetProperty(name) != null) { //3.给v设置值 typet.GetProperty(name).SetValue(t, pro.GetValue(v, null), null); } } return t; }
表达式树实现,效率非常高,可以进行几万次遍历进行测试执行的时间,注意返回方法委托,不要每次都去动态生成方法,不然性能可能更差了,此处利用了泛型委托的缓存机制,动态编译生成的类存入缓存,多次执行无需重新加载
public static FuncdeepCopy1 (V v) where T : class where V : class { //参照上面的结构进行!!!描述这个过程即最优解 Type typev = typeof(V); Type typet = typeof(T); //1.声明参数,就是{}里对象即参数 ParameterExpression pa = Expression.Parameter(typev, "person"); //2.声明一个属性绑定集合,也就是age=person.age,这种语法,存储起来 List bs = new List (); PropertyInfo[] propertyInfos = typev.GetProperties(); foreach (var pro in propertyInfos) { string name = pro.Name; if (typet.GetProperty(name) != null) { //3.person.xxx MemberExpression me = Expression.Property(pa, pro); //4.xxx = person.xxx MemberAssignment me2 = Expression.Bind(typet.GetProperty(name),me); bs.Add(me2); } } //5.new PersonA() //{ // age = person.age, // Name = person.Name, // Id = person.Id, //}; MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typet),bs); Expression > ex = Expression.Lambda >(memberInitExpression, pa); return ex.Compile(); }
对比:
Stopwatch w1 = new Stopwatch();
Stopwatch w2 = new Stopwatch();
w1.Start();
for (int i = 0; i < 1000000; i++)
{
PersonA personA1 = deepCopy(person);
}
w1.Stop();
Console.WriteLine(w1.ElapsedMilliseconds);
w2.Start();
for (int i = 0; i < 1000000; i++)
{
PersonA personA2 = func(person);
}
w2.Stop();
Console.WriteLine(w2.ElapsedMilliseconds);
Console.ReadLine();
结果:差距比较大的
2185 71



