表达式树,一种提高代码复用性和性能的方式
一、问题源起
在有些情况下,我们的计算逻辑跟传入数据的内部结构有关系,不仅不同的数据的计算逻辑不同,即使同一种数据结构的计算逻辑也会随时间变化;例如我们的大数据收集系统,需要根据前方业务人员配置的过滤表达式,来决定数据是否可以入库;那么我们就需要这个筛选的逻辑既要有通用性,也需要保证执行的高效性;那么表达式树或许是一种可能的选项;
二、什么是表达式树
表达式树是以类似树的结构来表达代码逻辑的一种方式;其中每一个节点都是一个表达式,例如一个方法调用或者赋值语句等。
我们可以编译表达式树,然后可以像普通方法那样执行。使用表达式树,我们可以动态的修改代码的执行逻辑,同时也可以基于LINQ创建动态查询并在不同类型的数据上执行。
我们可以使用C#提供的域名essions下的类来手动创建表达式。
三、使用Lambda表达式创建表达式树
只有将Lambda表达式赋值给Expression
Expression<Func<int, bool>> lambda = num => num < 5;
//生成的表达式树
.Lambda #Lambda1<域名`2[域名2,域名ean]>(域名2 $num) {
$num < 5
}
四、使用API创建表达式树
我们可以使用域名域名ession里提供的众多的静态工厂方法,根据需要创建不同类型的节点。
ParameterExpression numParam = 域名meter(typeof(int), "num");
ConstantExpression five = 域名tant(5, typeof(int));
BinaryExpression numLessThanFive = 域名Than(numParam, five);
Expression<Func<int, bool>> lambda1 =域名da<Func<int, bool>>(
numLessThanFive,
new ParameterExpression[] { numParam }
);
//生成的表达式树
.Lambda #Lambda1<域名`2[域名2,域名ean]>(域名2 $num) {
$num < 5
}
我们可以使用域名域名ession里提供的赋值及流程控制的众多API,来实现更加复杂的代码逻辑。
// Creating a parameter expression.
ParameterExpression value = 域名meter(typeof(int), "value");
// Creating an expression to hold a local variable.
ParameterExpression result = 域名meter(typeof(int), "result");
// Creating a label to jump to from a loop.
LabelTarget label = 域名l(typeof(int));
// Creating a method body.
BlockExpression block = 域名k(
// Adding a local variable.
new[] { result },
// Assigning a constant to a local variable: result = 1
域名gn(result, 域名tant(1)),
// Adding a loop.
域名(
// Adding a conditional block into the loop.
域名enElse(
// Condition: value > 1
域名terThan(value, 域名tant(1)),
// If true: result *= value --
域名iplyAssign(result,
域名DecrementAssign(value)),
// If false, exit the loop and go to the label.
域名k(label, result)
),
// Label to jump to.
label
)
);
// Compile and execute an expression tree.
var factorial = 域名da<Func<int, int>>(block, value);
//生成的表达式树
.Lambda #Lambda1<域名`2[域名2,域名2]>(域名2 $value) {
.Block(域名2 $result) {
$result = 1;
.Loop {
.If ($value > 1) {
$result *= $value--
} .Else {
.Break #Label1 { $result }
}
}
.LabelTarget #Label1:
}
}
五、编译表达式树
Expression
// Creating an expression tree.
Expression<Func<int, bool>> expr = num => num < 5;
// Compiling the expression tree into a delegate.
Func<int, bool> result = 域名ile();
// Invoking the delegate and writing the result to the console.
域名eLine(result(4));
// Prints True.
六、表达式树对方法关键部件的表达
我们有以下一个简单的方法,其中涉及方法的一些重要的基础部件
- 方法的传入参数x, y;
- 方法的局部变量sum;
- 方法的返回值类型以及返回操作;
static int Sum(int x, int y)
{
int sum = x + y;
return sum;
}
我们使用域名meter来声明需要传入参数的类型及名字;
var parax = 域名meter(typeof(int), "x");
var paray = 域名meter(typeof(int), "y");
我们使用域名able来声明执行过程中需要使用的局部变量;
var sum = 域名able(typeof(int));
我们可以使用LableTarget、GotoExpression、LableExpression来实现方法的return;
static Expression<Func<int, int, int>> SumExpression()
{
var x = 域名meter(typeof(int), "x");
var y = 域名meter(typeof(int), "y");
var sum = 域名able(typeof(int));
var add = 域名(x, y);
var assign = 域名gn(sum, add);
var labelTarget = 域名l(typeof(int));
var ret = 域名rn(labelTarget, sum);
var labelExpression = 域名l(labelTarget, 域名tant(0));
var block = 域名k(
new ParameterExpression[] { sum},
assign,
ret,
labelExpression
);
return 域名da<Func<int, int, int>>(block, x, y);
}
//生成的表达式树
.Lambda #Lambda1<域名`3[域名2,域名2,域名2]>(
域名2 $x,
域名2 $y) {
.Block(域名2 $var1) {
$var1 = $x + $y;
.Return #Label1 { $var1 };
.Label
0
.LabelTarget #Label1:
}
}
七、构建获取JSON对象字段的值的表达式
构建表达式的时候传入想要获取值的字段名字,执行表达式的时候可以获取对应对象的字段值。
public static Expression<Func<JObject, string>> ValueExpression(string name, ParameterExpression source = null)
{
//JObject obj = null;
//string name = null;
//string result = null;
//if (域名ainsKey(name))
//{
// var valueT = 域名alue(name);
// result = 域名ject<string>();
//}
//return result;
var result = 域名able(typeof(string));
var paraObj = source ?? 域名meter(typeof(JObject), "jObj");
var constName = 域名tant(name);
var getValue = typeof(JObject).GetMethod("GetValue", new Type[] { typeof(string) });
var getValueCall = 域名(paraObj, getValue, constName);
var valueT = 域名able(typeof(JToken));
var valueTAssign = 域名gn(valueT, getValueCall);
var toObject = typeof(JToken).GetMethod("ToObject", new Type[] { }).MakeGenericMethod(typeof(string));
var toObjectCall = 域名(valueT, toObject);
var resultAssign = 域名gn(result, toObjectCall);
var containBlock = 域名k(
valueTAssign,
resultAssign
);
var contain = typeof(JObject).GetMethod("ContainsKey", new Type[] { typeof(string) });
var containCall = 域名(paraObj, contain, constName);
var containCondition = 域名ition(containCall, containBlock, 域名gn(result, 域名tant(域名y)));
var target = 域名l(typeof(string));
var ret = 域名rn(target, result);
var block = 域名k(
new ParameterExpression[] { result, valueT },
containCondition,
ret,
域名l(target, 域名tant(域名y))
);
return 域名da<Func<JObject, string>>(block, paraObj);
}
八、构建Contain的表达式
构建的时候传入字段名字和测试是否包含的字符串;
public static Expression<Func<JObject, bool>> ContainsExpression(string name, string part, ParameterExpression source = null)
{
var result = 域名able(typeof(bool));
var paraObj = source ?? 域名meter(typeof(JObject), "jObj");
var constPart = 域名tant(part);
var fieldValue = 域名able(typeof(string));
var value = ValueExpression(name, paraObj).Body;
var fieldValueAssign = 域名gn(fieldValue, value);
var contains = typeof(string).GetMethod("Contains", new Type[] { typeof(string) });
var containsCall = 域名(fieldValue, contains, constPart);
var resultAssign = 域名gn(result, containsCall);
var target = 域名l(typeof(bool));
var ret = 域名rn(target, result);
var block = 域名k(
new ParameterExpression[] { result, fieldValue },
fieldValueAssign,
resultAssign,
ret,
//域名l(target, 域名tant(false))
域名l(target, result)
);
return 域名da<Func<JObject, bool>>(block, paraObj);
}
九、构建布尔表达式
根据实际的业务需要构建一个简单的布尔表达式
public static Expression<Func<JObject, bool>> BoolExpression()
{
var paraObj = 域名meter(typeof(JObject), "jObj");
var aContains = ContainsExpression("name", "man", paraObj);
var bContains = ContainsExpression("department", "dev", paraObj);
var and = 域名lso(域名, 域名);
return 域名da<Func<JObject, bool>>(and, paraObj);
}
十、执行布尔表达式
static void Main(string[] args)
{
var obj = 域名e("{" +
"name:\'mango\'," +
"department:\'dev\'"+
"}");
var e = BoolExpression();
var result = 域名ile()(obj);
域名eLine(result);
域名();
}
//测试输出结果 true
//生成的布尔表达式树
.Lambda #Lambda1<域名`2[域名.JObject,域名ean]>(域名.JObject $jObj) {
.Block(
域名ean $var1,
域名ng $var2) {
$var2 = .Block(
域名ng $var3,
域名.JToken $var4) {
.If (
.Call $域名ainsKey("name")
) {
.Block() {
$var4 = .Call $域名alue("name");
$var3 = .Call $域名ject()
}
} .Else {
$var3 = ""
};
.Return #Label1 { $var3 };
.Label
""
.LabelTarget #Label1:
};
$var1 = .Call $域名ains("man");
.Return #Label2 { $var1 };
.Label
$var1
.LabelTarget #Label2:
} && .Block(
域名ean $var5,
域名ng $var6) {
$var6 = .Block(
域名ng $var7,
域名.JToken $var8) {
.If (
.Call $域名ainsKey("department")
) {
.Block() {
$var8 = .Call $域名alue("department");
$var7 = .Call $域名ject()
}
} .Else {
$var7 = ""
};
.Return #Label3 { $var7 };
.Label
""
.LabelTarget #Label3:
};
$var5 = .Call $域名ains("dev");
.Return #Label4 { $var5 };
.Label
$var5
.LabelTarget #Label4:
}
}