表达式树,一种提高代码复用性的方式

一、问题源起

在有些情况下,我们的计算逻辑跟传入数据的内部结构有关系,不仅不同的数据的计算逻辑不同,即使同一种数据结构的计算逻辑也会随时间变化;例如我们的大数据收集系统,需要根据前方业务人员配置的过滤表达式,来决定数据是否可以入库;那么我们就需要这个筛选的逻辑既要有通用性,也需要保证执行的高效性;那么表达式树或许是一种可能的选项;

二、什么是表达式树

表达式树是以类似树的结构来表达代码逻辑的一种方式;其中每一个节点都是一个表达式,例如一个方法调用或者赋值语句等。

我们可以编译表达式树,然后可以像普通方法那样执行。使用表达式树,我们可以动态的修改代码的执行逻辑,同时也可以基于LINQ创建动态查询并在不同类型的数据上执行。

我们可以使用C#提供的System.Linq.Expressions下的类来手动创建表达式。

三、使用Lambda表达式创建表达式树

只有将Lambda表达式赋值给Expression类型的变量的时候,编译器会自动创建对应的表达式树;但是C#编译器只能为单句的Lambda表达式,这就大大限制了其使用的场景;

Expression<Func<int, bool>> lambda = num => num < 5;

//生成的表达式树
.Lambda #Lambda1<System.Func`2[System.Int32,System.Boolean]>(System.Int32 $num) {
    $num < 5
}

四、使用API创建表达式树

我们可以使用System.Linq.Expressions.Expression里提供的众多的静态工厂方法,根据需要创建不同类型的节点。

ParameterExpression numParam = Expression.Parameter(typeof(int), "num");
            ConstantExpression five = Expression.Constant(5, typeof(int));
            BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);
            Expression<Func<int, bool>> lambda1 =Expression.Lambda<Func<int, bool>>(
                numLessThanFive,
                new ParameterExpression[] { numParam }
             );
    
//生成的表达式树         
.Lambda #Lambda1<System.Func`2[System.Int32,System.Boolean]>(System.Int32 $num) {
    $num < 5
}

我们可以使用System.Linq.Expressions.Expression里提供的赋值及流程控制的众多API,来实现更加复杂的代码逻辑。

// Creating a parameter expression.  
ParameterExpression value = Expression.Parameter(typeof(int), "value");  
  
// Creating an expression to hold a local variable.
ParameterExpression result = Expression.Parameter(typeof(int), "result");  
  
// Creating a label to jump to from a loop.  
LabelTarget label = Expression.Label(typeof(int));  
  
// Creating a method body.  
BlockExpression block = Expression.Block(  
    // Adding a local variable.  
    new[] { result },  
    // Assigning a constant to a local variable: result = 1  
    Expression.Assign(result, Expression.Constant(1)),  
    // Adding a loop.  
        Expression.Loop(  
    // Adding a conditional block into the loop.  
           Expression.IfThenElse(  
    // Condition: value > 1  
               Expression.GreaterThan(value, Expression.Constant(1)),  
    // If true: result *= value --  
               Expression.MultiplyAssign(result,  
                   Expression.PostDecrementAssign(value)),  
    // If false, exit the loop and go to the label.  
               Expression.Break(label, result)  
           ),  
    // Label to jump to.  
       label  
    )  
);  
  
// Compile and execute an expression tree.  
var factorial = Expression.Lambda<Func<int, int>>(block, value);  
  



//生成的表达式树
.Lambda #Lambda1<System.Func`2[System.Int32,System.Int32]>(System.Int32 $value) {
    .Block(System.Int32 $result) {
        $result = 1;
        .Loop  {
            .If ($value > 1) {
                $result *= $value--
            } .Else {
                .Break #Label1 { $result }
            }
        }
        .LabelTarget #Label1:
    }
}

五、编译表达式树

Expression提供的Compile方法可以把表达式树编译为可执行的委托。

// Creating an expression tree.  
Expression<Func<int, bool>> expr = num => num < 5;  
  
// Compiling the expression tree into a delegate.  
Func<int, bool> result = expr.Compile();  
  
// Invoking the delegate and writing the result to the console.  
Console.WriteLine(result(4));  
  
// Prints True.  

六、表达式树对方法关键部件的表达

我们有以下一个简单的方法,其中涉及方法的一些重要的基础部件

  1. 方法的传入参数x, y;
  2. 方法的局部变量sum;
  3. 方法的返回值类型以及返回操作;
static int Sum(int x, int y)
{
	int sum = x + y;
	return sum;
}

我们使用Expression.Parameter来声明需要传入参数的类型及名字;

var parax = Expression.Parameter(typeof(int), "x");
var paray = Expression.Parameter(typeof(int), "y");

我们使用Expression.Variable来声明执行过程中需要使用的局部变量;

var sum = Expression.Variable(typeof(int));

我们可以使用LableTarget、GotoExpression、LableExpression来实现方法的return;

static Expression<Func<int, int, int>> SumExpression()
{
	var x = Expression.Parameter(typeof(int), "x");
	var y = Expression.Parameter(typeof(int), "y");
	var sum = Expression.Variable(typeof(int));
	var add = Expression.Add(x, y);
	var assign = Expression.Assign(sum, add);
	var labelTarget = Expression.Label(typeof(int));
	var ret = Expression.Return(labelTarget, sum);
	var labelExpression = Expression.Label(labelTarget, Expression.Constant(0));

	var block = Expression.Block(
		new ParameterExpression[] { sum},
		assign,
		ret,
		labelExpression
		);

	return Expression.Lambda<Func<int, int, int>>(block, x, y);
}

//生成的表达式树
.Lambda #Lambda1<System.Func`3[System.Int32,System.Int32,System.Int32]>(
    System.Int32 $x,
    System.Int32 $y) {
    .Block(System.Int32 $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 (obj.ContainsKey(name))
	//{
	//    var valueT = obj.GetValue(name);
	//    result = valueT.ToObject<string>();
	//}
	//return result;


	var result = Expression.Variable(typeof(string));
	var paraObj = source ?? Expression.Parameter(typeof(JObject), "jObj");
	var constName = Expression.Constant(name);

	var getValue = typeof(JObject).GetMethod("GetValue", new Type[] { typeof(string) });
	var getValueCall = Expression.Call(paraObj, getValue, constName);
	var valueT = Expression.Variable(typeof(JToken));
	var valueTAssign = Expression.Assign(valueT, getValueCall);

	var toObject = typeof(JToken).GetMethod("ToObject", new Type[] { }).MakeGenericMethod(typeof(string));
	var toObjectCall = Expression.Call(valueT, toObject);
	var resultAssign = Expression.Assign(result, toObjectCall);

	var containBlock = Expression.Block(
		valueTAssign,
		resultAssign
	);

	var contain = typeof(JObject).GetMethod("ContainsKey", new Type[] { typeof(string) });
	var containCall = Expression.Call(paraObj, contain, constName);
	var containCondition = Expression.Condition(containCall, containBlock, Expression.Assign(result, Expression.Constant(string.Empty)));


	var target = Expression.Label(typeof(string));
	var ret = Expression.Return(target, result);
	var block = Expression.Block(
		new ParameterExpression[] { result, valueT },
		containCondition,
		ret,
		Expression.Label(target, Expression.Constant(string.Empty))
		);

	return Expression.Lambda<Func<JObject, string>>(block, paraObj);
}

八、构建Contain的表达式

构建的时候传入字段名字和测试是否包含的字符串;

public static Expression<Func<JObject, bool>> ContainsExpression(string name, string part, ParameterExpression source = null)
{
	var result = Expression.Variable(typeof(bool));
	var paraObj = source ?? Expression.Parameter(typeof(JObject), "jObj");
	var constPart = Expression.Constant(part);
	var fieldValue = Expression.Variable(typeof(string));
	var value = ValueExpression(name, paraObj).Body;
	var fieldValueAssign = Expression.Assign(fieldValue, value);

	var contains = typeof(string).GetMethod("Contains", new Type[] { typeof(string) });
	var containsCall = Expression.Call(fieldValue, contains, constPart);
	var resultAssign = Expression.Assign(result, containsCall);

	var target = Expression.Label(typeof(bool));
	var ret = Expression.Return(target, result);
	var block = Expression.Block(
		new ParameterExpression[] { result, fieldValue },
		fieldValueAssign,
		resultAssign,
		ret,
		//Expression.Label(target, Expression.Constant(false))
		Expression.Label(target, result)
		);

	return Expression.Lambda<Func<JObject, bool>>(block, paraObj);
}

九、构建布尔表达式

根据实际的业务需要构建一个简单的布尔表达式

public static Expression<Func<JObject, bool>> BoolExpression()
{
	var paraObj = Expression.Parameter(typeof(JObject), "jObj");
	var aContains = ContainsExpression("name", "man", paraObj);
	var bContains = ContainsExpression("department", "dev", paraObj);
	var and = Expression.AndAlso(aContains.Body, bContains.Body);
	return Expression.Lambda<Func<JObject, bool>>(and, paraObj);
}

十、执行布尔表达式

static void Main(string[] args)
{
	var obj = JObject.Parse("{" +
		"name:'mango'," +
		"department:'dev'"+               
		"}");

	var e = BoolExpression();
	var result =  e.Compile()(obj);
	Console.WriteLine(result);

	Console.Read();
}

//测试输出结果 true

//生成的表达式树
.Lambda #Lambda1<System.Func`2[Newtonsoft.Json.Linq.JObject,System.Boolean]>(Newtonsoft.Json.Linq.JObject $jObj) {
    .Block(
        System.Boolean $var1,
        System.String $var2) {
        $var2 = .Block(
            System.String $var3,
            Newtonsoft.Json.Linq.JToken $var4) {
            .If (
                .Call $jObj.ContainsKey("name")
            ) {
                .Block() {
                    $var4 = .Call $jObj.GetValue("name");
                    $var3 = .Call $var4.ToObject()
                }
            } .Else {
                $var3 = ""
            };
            .Return #Label1 { $var3 };
            .Label
                ""
            .LabelTarget #Label1:
        };
        $var1 = .Call $var2.Contains("man");
        .Return #Label2 { $var1 };
        .Label
            $var1
        .LabelTarget #Label2:
    } && .Block(
        System.Boolean $var5,
        System.String $var6) {
        $var6 = .Block(
            System.String $var7,
            Newtonsoft.Json.Linq.JToken $var8) {
            .If (
                .Call $jObj.ContainsKey("department")
            ) {
                .Block() {
                    $var8 = .Call $jObj.GetValue("department");
                    $var7 = .Call $var8.ToObject()
                }
            } .Else {
                $var7 = ""
            };
            .Return #Label3 { $var7 };
            .Label
                ""
            .LabelTarget #Label3:
        };
        $var5 = .Call $var6.Contains("dev");
        .Return #Label4 { $var5 };
        .Label
            $var5
        .LabelTarget #Label4:
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是将后缀表达式转化成表达式的 C 语言代码,其中使用了栈(Stack)数据结构。 ```c #include <stdio.h> #include <stdlib.h> #include <ctype.h> struct node { char data; struct node *left; struct node *right; }; // 栈结构体 struct stack { int top; struct node **items; }; // 创建新节点 struct node *createNode(char data) { struct node *newNode = (struct node *)malloc(sizeof(struct node)); newNode->data = data; newNode->left = NULL; newNode->right = NULL; return newNode; } // 初始化栈 struct stack *createStack(int capacity) { struct stack *newStack = (struct stack *)malloc(sizeof(struct stack)); newStack->top = -1; newStack->items = (struct node **)malloc(capacity * sizeof(struct node *)); return newStack; } // 判断栈是否为空 int isEmpty(struct stack *s) { return s->top == -1; } // 判断栈是否已满 int isFull(struct stack *s) { return s->top == 99; } // 入栈 void push(struct stack *s, struct node *item) { if (isFull(s)) { printf("Stack is full\n"); return; } s->items[++s->top] = item; } // 出栈 struct node *pop(struct stack *s) { if (isEmpty(s)) { printf("Stack is empty\n"); return NULL; } return s->items[s->top--]; } // 从后缀表达式中构造表达式 struct node *constructTree(char postfix[]) { struct stack *s = createStack(strlen(postfix)); struct node *t, *t1, *t2; for (int i = 0; postfix[i] != '\0'; i++) { if (isdigit(postfix[i])) { t = createNode(postfix[i]); push(s, t); } else { t = createNode(postfix[i]); t1 = pop(s); t2 = pop(s); t->right = t1; t->left = t2; push(s, t); } } t = pop(s); free(s); return t; } // 中序遍历表达式 void inorder(struct node *t) { if (t) { inorder(t->left); printf("%c ", t->data); inorder(t->right); } } int main() { char postfix[] = "ab+cd-*"; struct node *root = constructTree(postfix); printf("Infix expression: "); inorder(root); return 0; } ``` 程序中定义了 `struct node` 结构体表示表达式的节点,`struct stack` 结构体表示栈的数据结构。`createNode` 函数用于创建新节点,`createStack` 函数用于初始化栈。 `isEmpty` 和 `isFull` 函数分别用于判断栈是否为空和已满。`push` 函数用于入栈,`pop` 函数用于出栈。 `constructTree` 函数用于将后缀表达式转化成表达式,程序中使用栈来辅助构造表达式。该函数遍历后缀表达式,如果当前字符是数字,则创建一个新节点并将其入栈。如果当前字符是操作符,则弹出栈顶的两个节点,将当前字符作为新节点的值,将弹出的两个节点分别作为新节点的左右子节点,最后将新节点入栈。 `inorder` 函数用于中序遍历表达式,输出中序遍历的结果即为中缀表达式。 在 `main` 函数中,定义了一个后缀表达式字符串 `postfix`,通过 `constructTree` 函数将其转换成表达式,并通过 `inorder` 函数输出中缀表达式

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值