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


    一、问题源起

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

    二、什么是表达式树

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

    我们可以编译表达式树,然后可以像普通方法那样执行。使用表达式树,我们可以动态的修改代码的执行逻辑,同时也可以基于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:
        }
    }
    
  • 相关阅读:
    安卓获取双IMEI
    NodeJS异步、同步 创建多层文件夹
    Winfrom 控件名称缩写
    Unobtrusive Ajax
    ID 为 17608的进程当前未运行
    欢迎
    路由
    VS快捷键
    Test
    并查集与带权并查集---由浅入深
  • 原文地址:https://www.cnblogs.com/wufengtinghai/p/15553543.html
Copyright © 2020-2023  润新知