表达式树是一种树形数据结构,通过动态语言运行时 (DLR) 将一组动态语言服务添加到公共语言运行时 (CLR),为静态类型语言添加动态特征。C#属于静态语言.简而言之,就是通过CLR引入DLR,DLR中包含了表达式树的功能,那么C#代码就具备了将静态代码转换成动态代码的功能.常用于一些运算逻辑的转换.将运算逻辑转换成数据结构缓存到内存中.比如通过表达式树缓存通过反射构建对象的过程,减少每次调用反射的性能消耗.具体参考DLR官方文档.
1、场景
假设有一个商品促销系统,促销系统的大致原理是维护商品的价格,商品价格会在一系列的促销规则加持下产生变动,大致的硬编码如下:
static void Main(string[] args) { //模拟张三买一件20元的商品 Promotion("张三", 20); } static float Promotion(string user,float price) { //促销活动1 if(user 参与促销活动1) price = price - 2; //促销活动2 if (user 参与促销活动2) price = price - 20; //促销活动3 if (user 参与促销活动3) price = price / 2; return price; }
如上代码能很好的完成需求,但是每个商品的促销活动都大不相同,且商品很多,所以客户提出这个促销活动的具体的扣价规则可以配置.显然上面的代码不满足需求.所以我们需要通过某种方式去存储计算规则.并且可以让客户自行配置.有一种方式是维护一张规则表,存储运算符号,然后通过如下方式:
//促销活动1 if (user 参与促销活动1) price=(float)(new DataTable().Compute("{price} - 2", ""));
通过将计算规则存入数据库.然后调用DataTable的Api实现计算.但是这种方式显然不够灵活,且如果复杂的计算流程,配置起来会比较麻烦且容易出错.下面来看看表达式树怎么做:
var rules = new List<Expression<Func<float, float>>>(); //促销规则1 rules.Add((price) =>price - 2); //促销规则2 rules.Add((price) => price - 20); //促销规则3 rules.Add((price) => price /2); float price = 20; rules.ForEach(f => { price=f.Compile()(price); }); Console.WriteLine("经过一系列促销活动后的最终价格为{0}", price); Console.ReadKey();
通过这种方式虽然能完成需求,但是这种方式任然需要通过硬编码的方式,显然不可取,且此时的表达式树虽然存储了所有的运算规则,但是这个规则只能是简单的数学运算,如果包含了负责的运算,则需要方法体,那么是不被允许的,如下图:
所以这种方式,需要将所有的运算逻辑全部转换成表达式树的形式即每一个节点都转换成表达式树,才可以,代码如下:
通多