c#:深入理解表达式树

环境:

  • window11
  • .net 6.0
  • vs2022

上一篇
《c#:表达式树概念及应用场景(Expression)》

罗列表达式树类型及应用场景:
在c#中共有85个类型的表达式树节点,如下:
《ExpressionType 枚举》
在这里插入图片描述

下面将一一罗列各个表达式类型的用法和场景

1. 常见表达式类型

1.1 ++=checked(+)

涉及到的表达式节点类型:

  • ExpressionType.Add:两个操作数相加;
  • ExpressionType.AddChecked:两个操作数相加,并检查计算是否溢出;
  • ExpressionType.AddAssign:两个数相加并将结果赋值给第一个数;
  • ExpressionType.AddAssignChecked:两个数相加并将结果赋值给第一个数,并检查是否溢出;
//add
int x = 0, y = 1;
Expression<Func<int>> add = () => x + y;
Expression<Func<int>> addExp = Expression.Lambda<Func<int>>(Expression.Add(Expression.Constant(x), Expression.Constant(y)));
object res = addExp.Compile()();// 输出: 1

//add checked
x = int.MaxValue;
y = 1;
int z = x + y; // 没有溢出检查,输出:-2147483648
//int z = checked(x + y); //因为加入了溢出检查,报错: System.OverflowException:“Arithmetic operation resulted in an overflow.”
Expression<Func<int>> addChecked = () => checked(x + y);
Expression<Func<int>> addCheckedExp = Expression.Lambda<Func<int>>(Expression.AddChecked(Expression.Constant(x), Expression.Constant(y)));
//res = addCheckedExp.Compile()(); //System.OverflowException:“Arithmetic operation resulted in an overflow.”

//AddAssign
var paraTmp = Expression.Parameter(typeof(int), "i");
Expression<Func<int, int>> addAssignExp = Expression.Lambda<Func<int, int>>(Expression.AddAssign(paraTmp, Expression.Constant(1)), new ParameterExpression[] { paraTmp });
res = addAssignExp.Compile()(1);// 输出2

//AddAssignChecked
var paraTmp2= Expression.Parameter(typeof(int), "i");
Expression<Func<int, int>> addAssignCheckedExp = Expression.Lambda<Func<int, int>>(Expression.AddAssignChecked(paraTmp, Expression.Constant(1)), new ParameterExpression[] { paraTmp });
//res = addAssignCheckedExp.Compile()(int.MaxValue);// System.OverflowException:“Arithmetic operation resulted in an overflow.”

上面基本上演示了+号相关的用法,不过我们注意到Expression.AddAssign()函数还有一个MethodInfo? methodLambdaExpression? conversion参数,这两个参数是可选的,可用来执行相加和赋值运算前的转换操作,虽然这个用的不多,但还是演示下它的用法:

//模拟带有特殊结构的运算
public class OpUtil
{
    public static StateStruct Add(string state, int scale)
    {
        return new StateStruct
        {
            State = state,
            Scale = scale
        };
    }

    public static string Convert(StateStruct state)
    {
        return $"{state.State}({state.Scale})";
    }

    public class StateStruct
    {
        public string State { get; set; }
        public int Scale { get; set; }
    }
}

//实际想达成的效果:"test"+1 => "test(1)"
//定义转换操作
Expression<Func<OpUtil.StateStruct, string>> convertionTmp = _ => OpUtil.Convert(_);
var para2 = Expression.Parameter(typeof(string), "i");
var addAssignConvert = Expression.Lambda<Func<string, string>>(Expression.AddAssign(para2, Expression.Constant(1), typeof(OpUtil).GetMethod("Add"), convertionTmp), new ParameterExpression[] { para2 });
res = addAssignConvert.Compile()("test");//输出: test(1)

1.2 --=checked(-)

涉及到的表达式节点类型:

  • ExpressionType.Subtract:两个操作数相减;
  • ExpressionType.SubtractChecked:两个操作数相减,并检查计算是否溢出;
  • ExpressionType.SubtractAssign:两个数相减并将结果赋值给第一个数;
  • ExpressionType.SubtractAssignChecked:两个数相减并将结果赋值给第一个数,并检查是否溢出;

这个和+号的操作类似,不再列举。

1.3 **=checked(*)

涉及到的表达式节点类型:

  • ExpressionType.Multiply:两个操作数相乘;
  • ExpressionType.MultiplyChecked:两个操作数相乘,并检查计算是否溢出;
  • ExpressionType.MultiplyAssign:两个数相乘并将结果赋值给第一个数;
  • ExpressionType.MultiplyAssignChecked:两个数相乘并将结果赋值给第一个数,并检查是否溢出;

这个和+号的操作类似,不再列举。

1.4 //=

涉及到的表达式节点类型:

  • ExpressionType.Divide:两个操作数相除;
  • ExpressionType.DivideAssign:两个数相除并将结果赋值给第一个数;

这个和+号的操作类似,只是没有了checked操作,应该是除法运算不会溢出,不再列举;

1.5 %%=

涉及到的表达式节点类型:

  • ExpressionType.Modulo:两个操作数相除取余;
  • ExpressionType.ModuloAssign:两个数相除取余并将结果赋值给第一个数;

这个和+号的操作类似,只是没有了checked操作,应该是除法取余运算不会溢出,不再列举;

1.6 关系或位运算 &&&&=

涉及到的表达式节点类型:

  • ExpressionType.And:两个操作数位运算或逻辑and;
  • ExpressionType.AndAlso:逻辑运算,短路and;
  • ExpressionType.AndAssign:两按位或逻辑 AND 复合赋值运算,如 C# 中的 (a &= b);
//and
Expression<Func<int, int, int>> andExp = (x, y) => x & y;
res = andExp.Compile()(0x01, 0x03);// 输出:1 b(0000_0001) & b(0000_0011) => b(0000_0001)

var paraTmp2 = Expression.Parameter(typeof(int), "x");
var paraTmp3 = Expression.Parameter(typeof(int), "y");
andExp = Expression.Lambda<Func<int, int, int>>(Expression.And(paraTmp2, paraTmp3), new ParameterExpression[] { paraTmp2, paraTmp3 });
res = andExp.Compile()(0x01, 0x03);// 输出: 1

Expression<Func<bool, bool, bool>> boolExp = (x, y) => x & y;
res = boolExp.Compile()(true, false);//输出: false

var paraTmp4 = Expression.Parameter(typeof(bool), "x");
var paraTmp5 = Expression.Parameter(typeof(bool), "y");
boolExp = Expression.Lambda<Func<bool, bool, bool>>(Expression.And(paraTmp4, paraTmp5), new ParameterExpression[] { paraTmp4, paraTmp5 });
res = boolExp.Compile()(true, false);// 输出: false

//AndAlso
Expression<Func<bool, bool, bool>> andAlsoExp = (x, y) => x && y;
res = andAlsoExp.Compile()(true, true);//输出: true

var paraTmp6 = Expression.Parameter(typeof(bool), "x");
var paraTmp7 = Expression.Parameter(typeof(bool), "y");
andAlsoExp = Expression.Lambda<Func<bool, bool, bool>>(Expression.And(paraTmp4, paraTmp5), new ParameterExpression[] { paraTmp4, paraTmp5 });
res = andAlsoExp.Compile()(true,true);//输出: true

//编译器自动自动转换委托的lambda表达式中不允许出现赋值运算
//AndAssign function(bool x,bool y){ x&=y; return x; }
var paraTmp8 = Expression.Parameter(typeof(bool), "x");
var paraTmp9 = Expression.Parameter(typeof(bool), "y");
var labelTarget = Expression.Label("ret");
var andAssignExp = Expression.Lambda<Func<bool, bool, bool>>(Expression.Block(Expression.AndAssign(paraTmp8, paraTmp9), paraTmp8), new ParameterExpression[] { paraTmp8, paraTmp9 });
res = andAssignExp.Compile()(true, true);

1.7 关系或位运算 ||||=

涉及到的表达式节点类型:

  • ExpressionType.Or:两个操作数位运算或逻辑or;
  • ExpressionType.OrElse:短路条件 OR 运算,如 C# 中的 (a || b) 或 Visual Basic 中的 (a OrElse b)。
  • ExpressionType.OrAssign:按位或逻辑 OR 复合赋值运算,如 C# 中的 (a |= b)。

这个和上面的&&类似,不再列举。

1.8 not运算符 !!=

涉及到的表达式节点类型:

  • ExpressionType.Not:按位求补运算或逻辑求反运算。 在 C# 中,它与整型的 (~a) 和布尔值的 (!a) 等效。
  • ExpressionType.NotEqual:不相等比较,如 C# 中的 (a != b) 。
  • ExpressionType.Equal:表示相等比较的节点,如 C# 中的 (a == b) 。

注意:没有b!=b2这种运算。

//not
Expression<Func<bool, bool>> notExp = x => !x;
res = notExp.Compile()(true);// 输出: false

var paraX = Expression.Parameter(typeof(bool), "x");
notExp = Expression.Lambda<Func<bool, bool>>(Expression.Not(paraX), new ParameterExpression[] { paraX });
res = notExp.Compile()(true);// 输出: false

//notEqual
Expression<Func<int, int, bool>> notEqualExp = (x, y) => x != y;
res = notEqualExp.Compile()(1, 1);//输出: false

var para12 = Expression.Parameter(typeof(int), "x");
var para13 = Expression.Parameter(typeof(int), "y");
notEqualExp = Expression.Lambda<Func<int, int, bool>>(Expression.NotEqual(para12, para13), new ParameterExpression[] { para12, para13 });
res = notEqualExp.Compile()(1, 1);//输出: false

//equal
Expression<Func<int, int, bool>> equalExp = (x, y) => x == y;
res = equalExp.Compile()(1, 1);//输出: true

var para14 = Expression.Parameter(typeof(int), "x");
var para15 = Expression.Parameter(typeof(int), "y");
equalExp = Expression.Lambda<Func<int, int, bool>>(Expression.Equal(para14, para15), new ParameterExpression[] { para14, para15 });
res = equalExp.Compile()(1, 1);//输出: true

1.9 关系运算 >>=

涉及到的表达式节点类型:

  • ExpressionType.GreaterThan:“大于”比较,如 (a > b)。
  • ExpressionType.GreaterThanOrEqual:“大于或等于”比较,如 (a >= b)。
//GreaterThan
Expression<Func<int, int, bool>> greaterThan = (x, y) => x > y;
res = greaterThan.Compile()(5, 4);//输出: true

var para45 = Expression.Parameter(typeof(int), "x");
var para46 = Expression.Parameter(typeof(int), "y");
greaterThan = Expression.Lambda<Func<int, int, bool>>(Expression.GreaterThan(para45, para46), new ParameterExpression[] { para45, para46 });
res = greaterThan.Compile()(5, 4);

//GreaterThanOrEqual
Expression<Func<int, int, bool>> greaterThanOrEqual = (x, y) => x >= y;
res = greaterThanOrEqual.Compile()(5, 5);//输出: true

var para47 = Expression.Parameter(typeof(int), "x");
var para48 = Expression.Parameter(typeof(int), "y");
greaterThanOrEqual = Expression.Lambda<Func<int, int, bool>>(Expression.GreaterThanOrEqual(para47, para48), new ParameterExpression[] { para47, para48 });
res = greaterThanOrEqual.Compile()(5, 4);

1.10 关系运算符 <<=

涉及到的表达式节点类型:

  • ExpressionType.LessThan:“小于”比较,如 (a < b)。
  • ExpressionType.LessThanOrEqual:“小于或等于”比较,如 (a <= b)。

这个和上面的类似,不再列举。

1.11 递增运算符: ++aa++

涉及到的表达式节点类型:

  • ExpressionType.PreIncrementAssign:一元前缀递增,如 (++a)。 应就地修改 a 对象。
  • ExpressionType.PostIncrementAssign:一元后缀递增,如 (a++)。 应就地修改 a 对象。
//PreIncrementAssign function pre(x)=>++x; lambda表达式树种不能直接写出来
var para59 = Expression.Parameter(typeof(int), "x");
var increPara = Expression.Lambda<Func<int, int>>(Expression.PreIncrementAssign(para59), new ParameterExpression[] { para59 });
res = increPara.Compile()(5);// 输出: 6

//PreIncrementAssign function pre(x)=>x++; lambda表达式树种不能直接写出来
var para60 = Expression.Parameter(typeof(int), "x");
var increPara2 = Expression.Lambda<Func<int, int>>(Expression.PostIncrementAssign(para60), new ParameterExpression[] { para60 });
res = increPara2.Compile()(5);// 输出: 5

1.12 递减运算符:--aa--

涉及到的表达式节点类型:

  • ExpressionType.PreDecrementAssign:一元前缀递减,如 (–a)。 应就地修改 a 对象。
  • ExpressionType.PostDecrementAssign:一元后缀递减,如 (a–)。 应就地修改 a 对象。

这个和上面的类似,不再列举。

1.13 按位或逻辑 XOR 运算:a^ba^=b

涉及到的表达式节点类型:

  • ExpressionType.ExclusiveOr:按位或逻辑 XOR 运算,如 C# 中的 (a ^ b) 和 Visual Basic 中的 (a Xor b)。
  • ExpressionType.ExclusiveOrAssign:按位或逻辑 XOR 复合赋值运算,如 c # 中 的 (^ = b) 。
//ExclusiveOr
Expression<Func<int, int, int>> exclusiveOrExp = (x, y) => x ^ y;
res = exclusiveOrExp.Compile()(0x02, 0x11);//b(0000_0010) ^ b(0001_0001) => b(0001_0011) =>19

var para85 = Expression.Parameter(typeof(int), "x");
var para86 = Expression.Parameter(typeof(int), "y");
exclusiveOrExp = Expression.Lambda<Func<int, int, int>>(Expression.ExclusiveOr(para85, para86), new ParameterExpression[] { para85, para86 });
res = exclusiveOrExp.Compile()(0x02, 0x11);//b(0000_0010) ^ b(0001_0001) => b(0001_0011) =>19

//ExclusiveOrAssign lambda中不允许出现赋值 function(x,y)=>x^=y;
var para87 = Expression.Parameter(typeof(int), "x");
var para88 = Expression.Parameter(typeof(int), "y");
var exclusiveOrAssignExp = Expression.Lambda<Func<int, int, int>>(Expression.Block(Expression.ExclusiveOrAssign(para87, para88), para87), new ParameterExpression[] { para87, para88 });
res = exclusiveOrAssignExp.Compile()(0x02, 0x11);//b(0000_0010) ^ b(0001_0001) => b(0001_0011) =>19

1.14 增加或减一:Decrement(i)Increment(i)

涉及到的表达式节点类型:

  • ExpressionType.Decrement:一元递减运算,如 C# 和 Visual Basic 中的 (a - 1)。 不应就地修改 a 对象。
  • ExpressionType.Increment:一元递增运算,如 C# 和 Visual Basic 中的 (a + 1)。 不应就地修改 a 对象。

这两个比较特殊,我们无法在ide中写出来(a-1会被识别为减法),但我们知道表达式树允许有这种操作即可。

//Decrement
// 自己在ide中写不出来
var varia2 = Expression.Parameter(typeof(int), "i");
var decre = Expression.Decrement(varia2);
var lambda23 = Expression.Lambda<Func<int, int>>(decre, varia2);
res = lambda23.Compile()(2);//输出: 1

1.15 移位运算:a>>ba>>=b

涉及到的表达式节点类型:

  • ExpressionType.RightShift:按位右移运算,如 (a >> b)。
  • ExpressionType.RightShiftAssign:按位右移复合赋值运算,如 (a >>= b)。
//RightShift
Expression<Func<int, int, int>> rightShiftExp = (x, y) => x >> y;
res = rightShiftExp.Compile()(2, 1);//输出: 4 b(0000_0010)>>1 => b(0000_0001)=1

var para99 = Expression.Parameter(typeof(int), "x");
var para100 = Expression.Parameter(typeof(int), "y");
rightShiftExp = Expression.Lambda<Func<int, int, int>>(Expression.RightShift(para99, para100), new ParameterExpression[] { para99, para100 });
res = rightShiftExp.Compile()(2, 1);//输出: 4 b(0000_0010)>>1 => b(0000_0001)=1

//RightShiftAssign function(x,y)=>x>>=y;
var para101 = Expression.Parameter(typeof(int), "x");
var para102 = Expression.Parameter(typeof(int), "y");
Expression<Func<int, int, int>> rightShiftAssignExp = Expression.Lambda<Func<int, int, int>>(Expression.RightShiftAssign(para101, para102), new ParameterExpression[] { para101, para102 });
res = rightShiftAssignExp.Compile()(2, 1);//输出: 4 b(0000_0010)>>1 => b(0000_0001)=1

1.16 移位运算:a<<ba<<=b

涉及到的表达式节点类型:

  • ExpressionType.LeftShift:按位左移运算,如 (a << b)。
  • ExpressionType.LeftShiftAssign:按位左移运算,如 (a << b)。

这个和上面的类似,不再列举。

1.17 算数求反:-a,checked(-a)

涉及到的表达式节点类型:

  • ExpressionType.Negate:算术求反运算,如 (-a)。 不应就地修改 a 对象。
  • ExpressionType.NegateChecked:算术求反运算,如 (-a),进行溢出检查。 不应就地修改 a 对象。
//Negate
var num11 = 23;
Expression<Func<int, int>> negateExp = (x) => -x;
res = negateExp.Compile()(2);//输出: -2

var para103 = Expression.Parameter(typeof(int), "x");
negateExp = Expression.Lambda<Func<int, int>>(Expression.Negate(para103), new ParameterExpression[] { para103 });
res = negateExp.Compile()(2);//输出: -2

//NegateChecked int.MaxValue=2147483647, int.MinValue=- 2147483648
Expression<Func<int, int>> negateCheckedExp = (x) => checked(-x);
//res = negateCheckedExp.Compile()(int.MinValue);//报错: System.OverflowException:“Arithmetic operation resulted in an overflow.”

var para104 = Expression.Parameter(typeof(int), "x");
negateCheckedExp = Expression.Lambda<Func<int, int>>(Expression.NegateChecked(para104), new ParameterExpression[] { para104 });
//res = negateCheckedExp.Compile()(int.MinValue);//报错: System.OverflowException:“Arithmetic operation resulted in an overflow.”

1.18 一元加法:+a

涉及到的表达式节点类型:

  • ExpressionType.UnaryPlus:一元加法运算,如 (+a)。 预定义的一元加法运算的结果是操作数的值,但用户定义的实现可以产生特殊结果。

这个比较特殊,感觉写这个没啥意思,一般情况下确实这样,不过文档上说可以自定义实现运算逻辑还是可以尝试的(最开始Addmethod参数),这里只列举简单情况。

//UnaryPlus
Expression<Func<int, int>> unaryPlusExp = (x) => +x;
res = unaryPlusExp.Compile()(2);//输出: 2

var para105 = Expression.Parameter(typeof(int), "x");
unaryPlusExp = Expression.Lambda<Func<int, int>>(Expression.UnaryPlus(para105), new ParameterExpression[] { para105 });
res = unaryPlusExp.Compile()(2);//输出: 2

1.19 null 合并运算:a??b

涉及到的表达式节点类型:

  • ExpressionType.Coalesce:表示 null 合并运算的节点,如 C# 中的 (a ?? b)。
//Coalesce
Expression<Func<int?, int>> coalesceExp = x => x ?? 0;
res = coalesceExp.Compile()(null);//输出: 0

var para120 = Expression.Parameter(typeof(int?), "x");
coalesceExp = Expression.Lambda<Func<int?, int>>(Expression.Coalesce(para120, Expression.Constant(0)), new ParameterExpression[] { para120 });
res = coalesceExp.Compile()(null);//输出: 0

1.20 三元运算符:x>y?x:y

涉及到的表达式节点类型:

  • ExpressionType.Conditional:条件运算,如 C# 中的 a > b ? a : b。
Expression<Func<int, int, int>> conditionalExp = (x, y) => x > y ? x : y;
res = conditionalExp.Compile()(1, 2);// 输出: 2

var para121 = Expression.Parameter(typeof(int), "x");
var para122 = Expression.Parameter(typeof(int), "y");
conditionalExp = Expression.Lambda<Func<int, int, int>>(Expression.Condition(Expression.GreaterThan(para121, para122), para121, para122), new ParameterExpression[] { para121, para122 });
res = conditionalExp.Compile()(1, 2);// 输出: 2

1.21 幂运算:Math.Pow(2,4)=16

这个比较特殊,我们在javascript,可以使用2**4表示求2的4次方,但在c#中没有这样的运算符,幂运算都统一采用Math.Pow()方法,但表达式类型中却有单独的一个幂运算表达式类型(应该是为Visual Basic准备用的)。

涉及到的表达式节点类型:

  • ExpressionType.Power:对某个数字进行幂运算的数学运算,如 Visual Basic 中的 (a ^ b)。
  • ExpressionType.PowerAssign:对某个数字进行幂运算的复合赋值运算,如 Visual Basic 中的(a ^= b)。
//Power 特殊,c#中没有幂运算符,使用的是 Math.Pow 表示
//Expression<Func<double,double,double>> power = (x,y) => x ** y;//报错
//下面两个等价
var para130 = Expression.Parameter(typeof(double), "x");
var para131 = Expression.Parameter(typeof(double), "y");
//Expression<Func<double, double, double>> power = Expression.Lambda<Func<double, double, double>>(Expression.Power(para130, para131), new ParameterExpression[] { para130, para131 });
Expression<Func<double, double, double>> power = Expression.Lambda<Func<double, double, double>>(Expression.Power(para130, para131, typeof(Math).GetMethod("Pow")), new ParameterExpression[] { para130, para131 });
res = power.Compile()(2, 4);//输出: 16

var para132 =Expression.Parameter(typeof(double), "x");
var para133 = Expression.Parameter(typeof(double), "y");
Expression<Func<double, double, double>> powerAssign = Expression.Lambda<Func<double, double, double>>(Expression.Block(
   Expression.PowerAssign(para130, para131),
   para130), new ParameterExpression[] { para130, para131 });
res = powerAssign.Compile()(2, 4);//输出: 16

1.22 条件值:IsFalseIsTrue

这两个比较特殊,我也不知道他们有什么用,直接在ide中也写不出来。

涉及到的表达式节点类型:

  • ExpressionType.IsTrue:true 条件值。
  • ExpressionType.IsFalse:false 条件值。
//IsTrue,IsFalse ide中写不出来
var paraIsTrue = Expression.Parameter(typeof(bool), "i");
var isTrue = Expression.Lambda<Func<bool, bool>>(Expression.IsTrue(paraIsTrue), paraIsTrue);
res = isTrue.Compile()(true);//true
res = isTrue.Compile()(false);//false

var paraIsFale = Expression.Parameter(typeof(bool), "i");
var isFalse = Expression.Lambda<Func<bool, bool>>(Expression.IsFalse(paraIsFale), paraIsFale);
res = isFalse.Compile()(true);//false
res = isFalse.Compile()(false);//true

1.23 装箱或类型测试:obj as objectobj is int

涉及到的表达式节点类型:

  • ExpressionType.TypeAs:显式引用或装箱转换,其中如果转换失败则提供 null,如 C# 中的 (obj as SampleType)。
  • ExpressionType.TypeIs:类型测试,如 C# 中的 obj is SampleType。
//TypeAs
Expression<Func<int, object>> typeAs = x => x as object;
res = typeAs.Compile()(1);//输出: 1

var para134 = Expression.Parameter(typeof(int), "x");
typeAs = Expression.Lambda<Func<int, object>>(Expression.TypeAs(para134, typeof(object)), new ParameterExpression[] { para134 });
res = typeAs.Compile()(1);

//TypeIs
Expression<Func<object, bool>> typeIs = x => x is int; // 注意:不能是 obj is int i,我也没有表达式中找到可以这个写的方法,'obj is int i' 应该只是ide上的语法糖
res = typeIs.Compile()(1);//输出: true

var para135 = Expression.Parameter(typeof(object), "x");
typeIs = Expression.Lambda<Func<object, bool>>(Expression.TypeIs(para135, typeof(int)), new ParameterExpression[] { para135 });
res = typeIs.Compile()(1);//输出: true

1.24 确切类型测试:TypeEqual

涉及到的表达式节点类型:

  • ExpressionType.TypeEqual:确切类型测试。

这个和ExpressionType.TypeIs的区别是TypeEqual是严格校验类型是否相等,而TypeIs是含继承或实现关系也可以。

 //TypeEqual
 // 这个不同于 obj is Fu, 这个是确切类型不能是继承或实现,而后者可以是继承或实现
 // TypeEqual 在IDE中无法直接写出来
 var typePara = Expression.Parameter(typeof(object), "x");
 var typeEqualExp = Expression.TypeEqual(typePara, typeof(Fu));

 Expression<Func<object, bool>> typeEq = Expression.Lambda<Func<object, bool>>(Expression.TypeEqual(typePara, typeof(Exception)), typePara);

 res = typeEq.Compile()(new object());//false
 res = typeEq.Compile()(new ArgumentException());//false
 res = typeEq.Compile()(new Exception());//true

1.25 反码运算:~a

涉及到的表达式节点类型:

  • ExpressionType.OnesComplement:二进制反码运算,如 C# 中的 (~a)。
//OnesComplement 在调试->窗口->内存->任意个 输入: &sb 即可查看实际内存
//byte sb = 1;//0000_0001;  
//sb = (byte)~sb;//1111_1110

Expression<Func<int, int>> onesComplementExp = x => ~~x;
res = onesComplementExp.Compile()(2);//两次反码得到自身: 2

var onesComplementPara = Expression.Parameter(typeof(int), "i");
onesComplementExp = Expression.Lambda<Func<int, int>>(Expression.OnesComplement(Expression.OnesComplement(onesComplementPara)), onesComplementPara);
res = onesComplementExp.Compile()(2);//两次反码得到自身: 2

1.26 转换数据类型:(int)bytechecked((int)byte)

涉及到的表达式节点类型:

  • ExpressionType.Convert:二强制转换或转换操作,如 C# 中的 (SampleType)obj。对于数值转换,如果转换后的值对于目标类型来说太大,这不会引发异常。
  • ExpressionType.ConvertChecked:强制转换或转换操作,如 C# 中的 (SampleType)obj。 对于数值转换,如果转换后的值与目标类型大小不符,则引发异常。
//Convert
Expression<Func<double, int>> convert = x => (int)x;
res = convert.Compile()(2.2);//输出: 2

var paraDouble = Expression.Parameter(typeof(double), "x");
convert = Expression.Lambda<Func<double, int>>(Expression.Convert(paraDouble, typeof(int)), new ParameterExpression[] { paraDouble });
res = convert.Compile()(2.2);//输出: 2

Expression<Func<object, Exception>> convert2 = x => (Exception)x;
res = convert2.Compile()(new ArgumentException());//输出: exception

var paraObject = Expression.Parameter(typeof(object), "x");
convert2 = Expression.Lambda<Func<object, Exception>>(Expression.Convert(paraObject, typeof(Exception)), new ParameterExpression[] { paraObject });
res = convert2.Compile()(new ArgumentException());//输出: exception

//ConvertChecked
Expression<Func<int, byte>> convertCheck = x => checked((byte)x);
//res = convertCheck.Compile()(1000);//溢出报错: System.OverflowException:“Arithmetic operation resulted in an overflow.”
res = convertCheck.Compile()(120);//正常转换

var paraInt = Expression.Parameter(typeof(int), "x");
convertCheck = Expression.Lambda<Func<int, byte>>(Expression.ConvertChecked(paraInt, typeof(byte)), new ParameterExpression[] { paraInt });
//res = convertCheck.Compile()(1000);//溢出报错: System.OverflowException:“Arithmetic operation resulted in an overflow.”
res = convertCheck.Compile()(120);//正常转换

1.27 常量和默认值:()=>1default(int)

涉及到的表达式节点类型:

  • ExpressionType.Constant:一个常量值。
  • ExpressionType.Default:默认值。
//Constant
Expression<Func<int>> constant = () => 1;
res = constant.Compile()();//输出: 1

constant = Expression.Lambda<Func<int>>(Expression.Constant(1));
res = constant.Compile()();//输出:1 

//Default
Expression<Func<int>> d = () => default(int);
res = d.Compile()();// 输出: 0
d = Expression.Lambda<Func<int>>(Expression.Default(typeof(int)));
res = d.Compile()();// 输出: 0

1.28 赋值运算符:i=1

涉及到的表达式节点类型:

  • ExpressionType.Assign:赋值运算,如 (a = b)。

在lambda表达式中是不允许出现赋值运算的,所以我们不能依赖ide自动处理我们写的委托。

//首先定义个类
public class Model
{
     public int Id { get; set; }
}
//下面模拟函数:function(Model i,Model j){ j.Id=i.Id=8;return j;}
var model3 = new Model() { Id = 3 };
var model4 = new Model() { Id = 4 };

var para3 = Expression.Parameter(typeof(Model), "i");
var para4 = Expression.Parameter(typeof(Model), "j");
var memberAccess3 = Expression.MakeMemberAccess(para3, typeof(Model).GetProperty("Id"));
var memberAccess4 = Expression.MakeMemberAccess(para4, typeof(Model).GetProperty("Id"));
var assign = Expression.Assign(memberAccess3, Expression.Constant(8));
var assign2 = Expression.Assign(memberAccess4, assign);
var lamnbda = Expression.Lambda<Action<Model, Model>>(assign2, para3, para4);
lamnbda.Compile()(model3, model4);
Debug.Assert(model3.Id == 8);
Debug.Assert(model4.Id == 8);

1.29 类、数组、集合的创建初始化:new Model()new Model(){Id=1}new List<int>()

为什么将这些放在一块呢?因为是它们看着很相似。

涉及到的表达式节点类型:

  • ExpressionType.New:调用构造函数创建新对象的运算,如 new SampleType()。
  • ExpressionType.NewArrayBounds:创建新数组(其中每个维度的界限均已指定)的运算,如 C# 中的 new SampleType[dim1, dim2]。
  • ExpressionType.NewArrayInit:创建新的一维数组并从元素列表中初始化该数组的运算,如 C# 中的 new SampleType[]{a, b, c}。
  • ExpressionType.MemberInit:创建新的对象并初始化其一个或多个成员的运算,如 C# 中的 new Point { X = 1, Y = 2 }。
  • ExpressionType.ListInit:创建新的 IEnumerable 对象并从元素列表中初始化该对象的运算,如 C# 中的 new List<SampleType>(){ a, b, c }。
//New
Expression<Func<Model>> newExp = () => new Model();
res = newExp.Compile()();

newExp = Expression.Lambda<Func<Model>>(Expression.New(typeof(Model).GetConstructor(new Type[0])));
res = newExp.Compile()();

//NewArrayInit
Expression<Func<int[]>> newArrayInit = () => new int[] { 1, 2, 3 };
res = newArrayInit.Compile()();

newArrayInit = Expression.Lambda<Func<int[]>>(
    Expression.NewArrayInit(typeof(int),
        Expression.Constant(1),
        Expression.Constant(2),
        Expression.Constant(3)));
res = newArrayInit.Compile()();

//NewArrayBounds
Expression<Func<int[,,]>> newArrayBounds = () => new int[2, 3, 2];
res = newArrayBounds.Compile()();

//MemberInit
Expression<Func<Model>> memberInit = () => new Model { Id = 10 };
memberInit = Expression.Lambda<Func<Model>>(Expression.MemberInit(
    Expression.New(typeof(Model).GetConstructor(new Type[0])),
    Expression.Bind(typeof(Model).GetProperty("Id"), Expression.Constant(10)))
);
res = memberInit.Compile()();

//ListInit
Expression<Func<List<int>>> listInit = () => new List<int>()
{
    1,2,3
};
res = listInit.Compile()();

//这个因为没有初始化集合元素,所以还是 New
listInit = () => new List<int>();

//这个因为只写了个花括号,被当做了 MemberInit
listInit = () => new List<int>() { };

listInit = Expression.Lambda<Func<List<int>>>(
    Expression.ListInit(
        Expression.New(typeof(List<int>).GetConstructor(new Type[0])),
        Expression.ElementInit(typeof(List<int>).GetMethod("Add"), Expression.Constant(1)),
        Expression.ElementInit(typeof(List<int>).GetMethod("Add"), Expression.Constant(2)),
        Expression.ElementInit(typeof(List<int>).GetMethod("Add"), Expression.Constant(3))
    ));
res = listInit.Compile()();

另外,有个方法Expression.MemberBind(...),文档上说 “创建一个表示递归初始化某个成员的成员的 MemberMemberBinding。”,但我没有理解它的意思,而且也找不到应用场景,先略过。。。

1.30 数组访问:arr.Lengtharr[1]arr[1]=2

涉及到的表达式节点类型:

  • ExpressionType.ArrayLength:获取一维数组长长度的运算,如 array.Length。
  • ExpressionType.ArrayIndex:一维数组中的索引运算,如 C# 中的 array[index]。
//ArrayLength
Expression<Func<int[], int>> arrayLen = i => i.Length;
res = arrayLen.Compile()(new int[] { 1, 2 });//输出: 2

var para111 = Expression.Parameter(typeof(int[]), "i");
arrayLen = Expression.Lambda<Func<int[], int>>(Expression.ArrayLength(para111), new ParameterExpression[] { para111 });
res = arrayLen.Compile()(new int[] { 1, 2 });//输出: 2

//ArrayIndex:注意:Expression.ArrayIndex创建的表达式不能进行赋值
Expression<Func<string[], string>> arrayIndex = i => i[1];
res = arrayIndex.Compile()(new string[3] { "刘备", "关羽", "张飞" });//输出: 关羽

var para112 = Expression.Parameter(typeof(string[]), "i");
arrayIndex = Expression.Lambda<Func<string[], string>>(Expression.ArrayIndex(para112, Expression.Constant(1)), new ParameterExpression[] { para112 });
res = arrayIndex.Compile()(new string[3] { "刘备", "关羽", "张飞" });//输出: 关羽

//赋值时 使用Expression.ArrayAccess
var para113 = Expression.Parameter(typeof(string[]), "i");
var arrayAccess = Expression.ArrayAccess(para113, Expression.Constant(1));
var assignAccess = Expression.Assign(arrayAccess, Expression.Constant("曹操"));
arrayIndex = Expression.Lambda<Func<string[], string>>(Expression.Block(assignAccess, arrayAccess), new ParameterExpression[] { para113 });
res = arrayIndex.Compile()(new string[3] { "刘备", "关羽", "张飞" });//输出: 曹操

1.31 方法和委托调用:

涉及到的表达式节点类型:

  • ExpressionType.Call:方法调用,如在 obj.sampleMethod() 表达式中。
  • ExpressionType.Invoke:调用委托或 lambda 表达式的运算,如 sampleDelegate.Invoke()。

这两个看着比较像,同样都是调用方法,但是它们侧重点不同,Call就像是普通的方法调用,而Invoke是针对委托或Expression的调用。

//Invoke
Func<string> func5 = () => "Hello";
Expression<Func<string>> invoke = () => func5();
res = invoke.Compile()();//输出: "Hello"

Expression<Func<string>> funcExp = () => "Hello";
Expression<Func<string>> invoke2 = Expression.Lambda<Func<string>>(Expression.Invoke(funcExp));
res = invoke2.Compile()();//输出: "Hello"

//Call
//先定义类
public class ModelCall
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Show()
    {
        return $"id={Id},name={Name}";
    }
}

//演示Call
var inst = new ModelCall() { Id = 1, Name = "小明" };
Expression<Func<string>> call = () => inst.Show();
res = call.Compile()();//输出: id=1,name=小明

call = Expression.Lambda<Func<string>>(Expression.Call(Expression.Constant(inst), typeof(ModelCall).GetMethod("Show")));
res = call.Compile()();//输出: id=1,name=小明

1.32 访问实例成员:model.Id

涉及到的表达式节点类型:

  • ExpressionType.MemberAccess:从字段或属性进行读取的运算,如 obj.SampleProperty。
//MemberAccess
var model9 = new Model() { Id = 9 };
Expression<Func<int>> memberAccess = () => model9.Id;
res = memberAccess.Compile()();//输出: 9

memberAccess = Expression.Lambda<Func<int>>(Expression.MakeMemberAccess(Expression.Constant(model9), typeof(Model).GetProperty("Id")));
res = memberAccess.Compile()();//输出: 9

//如果想进行赋值,结合普通的Assign即可
var memberSet = Expression.Lambda<Action>(Expression.Assign(
    Expression.MakeMemberAccess(Expression.Constant(model9), typeof(Model).GetProperty("Id")), Expression.Constant(2)));
memberSet.Compile()();
Debug.Assert(model9.Id == 2);

1.33 Lambda表达式

这个我们最熟悉了,它是所有ide自动编译的表达式顶点。

涉及到的表达式节点类型:

  • ExpressionType.Lambda:lambda 表达式,如 C# 中的 a => a + a。
//应该不需要代码演示了吧
//lambda
Expression<Func<int>> lambdaExp = () => 1;

lambdaExp = Expression.Lambda<Func<int>>(Expression.Constant(1));

1.34 块表达式:{}

涉及到的表达式节点类型:

  • ExpressionType.Block:表达式块。
//Block 无法依赖ide自动编译块表达式
//下面的代码模拟了如下代码块
//var model12 = new Model() { Id = 4 };
//model12.Id = 6;
//Console.WriteLine(model12.Id);

var vari1 = Expression.Variable(typeof(Model), "model12");
var new1 = Expression.MemberInit(
   Expression.New(typeof(Model).GetConstructor(new Type[0])),
   Expression.Bind(typeof(Model).GetProperty("Id"), Expression.Constant(4))
);
var assign1 = Expression.Assign(vari1, new1);
var memberAccess1 = Expression.MakeMemberAccess(vari1, typeof(Model).GetProperty("Id"));
var assign12 = Expression.Assign(memberAccess1, Expression.Constant(6));
var call1 = Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }), memberAccess1);

var block = Expression.Block(new ParameterExpression[] { vari1 }, vari1, assign1, assign12, call1);

var lambda12 = Expression.Lambda<Action>(block);
lambda12.Compile()();//控制台输出: 6

1.35 方法参数和局部变量:function(int x){int y=0;}

涉及到的表达式节点类型:

  • ExpressionType.Parameter:对在表达式上下文中定义的参数或变量的引用。

注意:
在表达式树中,c#使用同一个节点类型同时表示方法参数和局部变量,这可能有点费解(因为方法参数和局部变量肯定不同)。

c#应该是这样考虑的: 它们的本质都是一个引用,不过是引用来源的位置不同罢了,一个是来自本身(局部变量),一个是来自调用者传递(方法参数)。

那么如何区分方法参数和局部变量呢?
既然它们本质相同,所以它们在创建上应该没多大差别(方法Expression.Parameter(...)Expression.Variable(...)基本相同),有差别的是我们把它写入的位置!!!

下面演示如何定义方法参数和局部变量:

  • 方法参数
    /* 模拟方法
         int Demo(int i)
         {
             return i + 1;
         }
    */
    var paraI = Expression.Parameter(typeof(int), "i");
    Expression<Func<int, int>> DemoExp = Expression.Lambda<Func<int, int>>(
        Expression.Block(//其实也可以不用Block包裹
            Expression.Add(paraI, Expression.Constant(1))
        ),
        new ParameterExpression[] { paraI });
    res = DemoExp.Compile()(0);//输出: 1
    
    //注意 paraI 用在了Expression.Lambda(...)方法中
    
    我们可以在调试中看到:
    在这里插入图片描述
  • 局部变量
    /* 模拟方法
    int Demo2()
    {
        int i = 0;
        return i + 1;
    }
    */
    var variI = Expression.Parameter(typeof(int), "i");
    Expression<Func<int>> Demo2Exp = Expression.Lambda<Func<int>>(
        Expression.Block(new ParameterExpression[] { variI },
            Expression.Assign(variI, Expression.Constant(0)), // i=0
            Expression.Add(variI, Expression.Constant(1)) // i=1
        ));
    res = Demo2Exp.Compile()();//输出: 1
    
    //注意 variI 用在了Expression.Block(...)方法中,而不是 Expression.Lambda(...) 方法中
    
    我们可以在调试中观察到:
    在这里插入图片描述

看了上面,我们应该知道怎样在表达式树中创建和区分方法参数和局部变量了吧。

关于Expression.Variable和Expression.Parameter

上面说了,它们两个基本一致,这里就从源码看下:

在这里插入图片描述
在这里插入图片描述
可以看到,它们除了参数是否传递引用(ref关键字)时的细微差别,本质都一样,都是创建了ParameterExpression实例。而我们方法中很少有使用ref的,所以一般情况下,Expression.ParameterExpression.Variable并无差别。

1.36 索引运算:

涉及到的表达式节点类型:

  • ExpressionType.Index:索引运算或访问使用参数的属性的运算。

这种节点类型可用来表示两种运算,一个是模型的索引器,一个是数组的索引运算;

  • 模型索引器:

    //准备一个带索引器的类
    public class ModelIndex
    {
        public Dictionary<int, string> dic { get; set; }
        public string this[int i]
        {
            get => dic.ContainsKey(i) ? dic[i] : string.Empty;
            set => dic[i] = value;
        }
    }
    
    //准备一个实例
    var modelIndex = new ModelIndex()
    {
       dic = new Dictionary<int, string>() { { 1, "小明" }, { 2, "小红" } }
    };
    //1. 访问索引器
    //1.1 使用ide自动将委托编译成表达式树,ide会编译成方法调用(Call),即调用ModelIndex的get_Item(int i)方法,而不是Index // 索引器默认使用带参数的Item属性实现
    Expression<Func<ModelIndex, int, string>> modelIndexExp = (modelIndex, index) => modelIndex[index];
    res = modelIndexExp.Compile()(modelIndex, 1);//输出: 小明
    
    //1.2 使用Expression.MakeIndex可以创建出Index类型的节点,和上面的效果是一样的
    var paraModelIndex = Expression.Parameter(typeof(ModelIndex), "modelIndex");
    var index = Expression.Parameter(typeof(int), "index");
    modelIndexExp = Expression.Lambda<Func<ModelIndex, int, string>>(
       Expression.MakeIndex(paraModelIndex, typeof(ModelIndex).GetProperty("Item"), new[] { index }),
       new[] { paraModelIndex, index });
    res = modelIndexExp.Compile()(modelIndex, 1);//输出: 小明
    
    //2. 使用索引器赋值
    var modelIndex2 = Expression.MakeIndex(paraModelIndex, typeof(ModelIndex).GetProperty("Item"), new[] { index });
    
    modelIndexExp = Expression.Lambda<Func<ModelIndex, int, string>>(
       Expression.Block(
           Expression.Assign(modelIndex2, Expression.Constant("小刚")),
           modelIndex2
       ),
       new[] { paraModelIndex, index });
    res = modelIndexExp.Compile()(modelIndex, 1);//输出: 小刚
    
  • 数组索引

    //节点类型是 ArrayIndex
    Expression<Func<int[], int, int>> arrIndexExp = (arr, index) => arr[index];
    res = arrIndexExp.Compile()(new int[] { 1, 2 }, 1);//输出: 2
    
    //节点类型的Index
    var paraIntArr = Expression.Parameter(typeof(int[]), "arr");
    var paraIntIndex = Expression.Parameter(typeof(int), "index");
    var arrInex3 = Expression.MakeIndex(paraIntArr, typeof(int[]).GetProperty("Item"), new[] { paraIntIndex });
    arrIndexExp = Expression.Lambda<Func<int[], int, int>>(arrInex3, new[] { paraIntArr, paraIntIndex });
    res = arrIndexExp.Compile()(new int[] { 1, 2 }, 1);//输出: 2
    
    //赋值时:不能赋值
    arrIndexExp = Expression.Lambda<Func<int[], int, int>>(Expression.Block(
        Expression.Assign(arrIndexExp, Expression.Constant(5)),
        arrIndexExp
        ), new[] { paraIntArr, paraIntIndex });//System.ArgumentException:“Expression must be writeable Arg_ParamName_Name”
    

对于数组索引的访问还有Expression.ArrayAccess(...)Expression.ArrayIndex(...),那么它们和Index类型的节点什么关系呢?

可以说,Expression.ArrayAccess(...)Expression.ArrayIndex(...)是专门给数组准备的,而Expression.MakeIndex(...)既可以应用于数组也可以应用于模型。
其中,仅Expression.ArrayAccess(...)可以对数组元素进行赋值。关于它们参考上面介绍:《1.30 数组访问》

1.37 标签和跳转:label:goto

涉及到的表达式节点类型:

  • ExpressionType.Label:标签。
  • ExpressionType.Goto:“转到”表达式,如 C# 中的 goto Label。

这种语法在c#中不推荐使用,但我们应该了解下,示例代码:

//label、goto的使用
//Label Goto
var labeli = 1;
if (labeli == 1)
{
    goto label1;
}
else if (labeli == 2)
{
    goto label2;
}
else
{
    goto labelother;
}

label1:
{
    Console.WriteLine("label1");
    goto labelother;
}

label2:
{
    Console.WriteLine("label2");
    goto labelother;
}
labelother:
{
    Console.WriteLine("labelother");
}
/* 输出
	label1
	labelother
*/

下面就用表达式树模拟下上面的代码:

Expression.Label(typeof(int));
var label1 = Expression.Label("label1");
var label2 = Expression.Label("label2");
var labelother = Expression.Label("labelother");


var labeliExp = Expression.Parameter(typeof(int), "labeli");
var blockLabelExp = Expression.Block(
      Expression.IfThenElse(
          Expression.Equal(Expression.Constant(1), labeliExp),
          Expression.Goto(label1),
          Expression.IfThenElse(
              Expression.Equal(Expression.Constant(2), labeliExp),
              Expression.Goto(label2),
              Expression.Goto(labelother)
          ))
      , Expression.Label(label1)
      , Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("label1"))
      , Expression.Goto(labelother)
      , Expression.Label(label2)
      , Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("label2"))
      , Expression.Goto(labelother)
      , Expression.Label(labelother)
      , Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("labelother"))
      );

var lableLambda = Expression.Lambda<Action<int>>(blockLabelExp, labeliExp);
var actLabel = lableLambda.Compile();
actLabel(1); actLabel(2); actLabel(3);

注意: ExpressionType.Goto表示的是跳转,这是一个大类,往下细分还有:returnbreakcontinuegoto四个小类,看下面定义:
在这里插入图片描述
这四个小类都用GotoExpression表示,分别对应Expression.Return(),Expression.Break(),Expression.Continue(),Expression.Goto()四个方法,如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.38 循环 for

涉及到的表达式节点类型:

  • ExpressionType.Loop:一个循环,例如 for 或 while。

注意,下面以for循环举例,其他循环如do while都类似:

//Loop
//for (var i = 0; i < 5;i++)
//{
//    Console.WriteLine(i);
//}
//Console.WriteLine("end loop");

var parai = Expression.Parameter(typeof(int), "i");
var breakLabel = Expression.Label("break");
var continueLabel = Expression.Label("continue");

var loopInit = Expression.Assign(parai, Expression.Constant(0));//i=0
var loopExp = Expression.Loop(
     Expression.Block(
         Expression.IfThenElse(//if
             Expression.LessThan(parai, Expression.Constant(5)),//i<5
             Expression.Block(//then
                Expression.Call(null, typeof(Console).GetMethod("WriteLine", new[] { typeof(int) }), parai)//Console.WriteLine(i);
                , Expression.PostIncrementAssign(parai)//i++
                , Expression.Continue(continueLabel)//continue
             ),
             Expression.Goto(breakLabel))//else break
      ), breakLabel, continueLabel);
var total = Expression.Block(new[] { parai },
    loopInit,
    loopExp,
    Expression.Call(null, typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }), Expression.Constant("end loop"))
    );
var loopAct = Expression.Lambda<Action>(total).Compile();
loopAct();

/** 输出
0
1
2
3
4
end loop
*/

其中调试组装后的表达式数如下:
在这里插入图片描述

1.39 switch

涉及到的表达式节点类型:

  • ExpressionType.Switch:多分支选择运算,如 C# 中的 switch。
 //Switch
//int si = 2;
//switch (si)
//{
//    case 1:
//        {
//            Console.WriteLine("case 1");
//            break;
//        }
//    case 2:
//        {
//            Console.WriteLine("case 2");
//            break;

//        }
//    case 3:
//    case 4:
//    case 5:
//        {
//            Console.WriteLine("case 3,case4,case5");
//            break;
//        }
//    default:
//        {
//            Console.WriteLine("default");
//            break;
//        }
//}

var paraSwitch = Expression.Parameter(typeof(int), "i");
var switchExp = Expression.Switch(
      paraSwitch//switch
      , Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("default")) //default
      , Expression.SwitchCase(
          Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("case 1")),
          Expression.Constant(1))//case 1
      , Expression.SwitchCase(
          Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("case 2")),
          Expression.Constant(2))//case 2
      , Expression.SwitchCase(
          Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("case 3,case4,case5")),
          Expression.Constant(3),
          Expression.Constant(4),
          Expression.Constant(5))//case 3,case4,case5
      );
Expression<Action<int>> switchLambda = Expression.Lambda<Action<int>>(switchExp, paraSwitch);
var act = switchLambda.Compile();
act(1); //case 1
act(2); //case 2
act(3); //case 3,case4,case5
act(4); //case 3,case4,case5
act(5); //case 3,case4,case5
act(0);//default

1.40 try-catch-finally throw

涉及到的表达式节点类型:

  • ExpressionType.Throw:引发异常的运算,如引发新异常()。
  • ExpressionType.Try:try-catch 表达式。
//模拟下面的代码
/*
int i = 0;
try
{
    Console.WriteLine("try");
    if (i == 0) throw new NotImplementedException("测试未实现!");
    if (i == 1) throw new ArgumentNullException("测试参数!");
    Console.WriteLine("try-end");
}
catch (NotImplementedException ex)
{
    Console.WriteLine("测试未实现 catch" + ex.Message);
}
catch (ArgumentNullException ex)
{
    Console.WriteLine("测试参数 catch" + ex.Message);
}
finally
{
    Console.WriteLine("finally");
}
*/

var para = Expression.Parameter(typeof(int), "i");//int i;
var para2 = Expression.Parameter(typeof(NotImplementedException), "ex");
var para3 = Expression.Parameter(typeof(ArgumentNullException), "ex");
var tryExp = Expression.TryCatchFinally(Expression.Block(
     Expression.Call(null, typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }), Expression.Constant("try")),//Console.WriteLine("try");
     Expression.IfThen(
        Expression.Equal(para, Expression.Constant(0)), //if (i == 0)
        Expression.Throw(//throw
            Expression.New(//new NotImplementedException("测试未实现!")
                typeof(NotImplementedException).GetConstructor(new[] { typeof(string) }), 
                Expression.Constant("测试未实现!")))
        ),
     Expression.IfThen(
         Expression.Equal(para, Expression.Constant(1)), //if (i==1)
         Expression.Throw(//throw
             Expression.New(//new ArgumentNullException("测试参数!")
                 typeof(ArgumentNullException).GetConstructor(new[] { typeof(string) }), 
                 Expression.Constant("测试参数!"))))
     ),
     Expression.Call(null, //Console.WriteLine("try-end");
        typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }), 
        Expression.Constant("finally")
     ),
     Expression.MakeCatchBlock(typeof(NotImplementedException), para2,//catch (NotImplementedException ex)
         Expression.Call(null, //Console.WriteLine(
            typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }), 
            Expression.Add(
                Expression.Constant("测试未实现 catch"), 
                Expression.MakeMemberAccess(para2, typeof(NotImplementedException).GetProperty("Message")), //ex.Message
                typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }))), //"+" 使用 string.Concat方法实现
     null),
     Expression.MakeCatchBlock(typeof(ArgumentNullException), para3,//catch (ArgumentNullException ex)
         Expression.Call(null,  //Console.WriteLine(
            typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }), 
            Expression.Add(
                Expression.Constant("测试参数 catch"), 
                Expression.MakeMemberAccess(para3, typeof(ArgumentNullException).GetProperty("Message")), //ex.Message
                typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }))),//"+" 使用 string.Concat方法实现
     null)
     );
var lambdaExp = Expression.Lambda<Action<int>>(tryExp, new[] { para });
lambdaExp.Compile()(0);

/* 输出
try
测试未实现 catch测试未实现!
finally
*/

注意: 上面抛出异常除了使用的是Expression.Throw(...)方法,还可以使用Expression.ReThrow(),它们分别相当于:throw new Exceptionthrow;,两种抛出异常方式有很大的差别,可以参考:《c#:异常的堆栈信息 & throw和throw ex的区别》

关于Expression.ReThrow这里就不再演示。

1.41 拆箱:Unbox

涉及到的表达式节点类型:

  • ExpressionType.Unbox:取消装箱值类型运算,如 MSIL 中的 unbox 和 unbox.any 指令。

从描述中,我们也可以看到,这个节点类型是给MSIL准备的,一般我们在IDE中是不会写装箱、拆箱的,因为IDE会自动编译处理。

为了演示,还是举一个简单的例子:

var para = Expression.Parameter(typeof(object), "i");
varunBox = Expression.Unbox(para, typeof(int));
var func = Expression.Lambda<Func<object, int>>(unBox, new[] { para });
var res = func.Compile()(2);

在这里插入图片描述

1.41 其他表达式类型

上面已经列举了绝大部分的表达式类型,但还有一些表达式类型因为不知道其作用没有写出示例代码,如下:

  • ExpressionType.Extension:扩展表达式。(注:估计是预留扩展用的,可以让我们自己实现新的表达式)。
  • ExpressionType.Quote:具有类型为 Expression 的常量值的表达式。 Quote 节点可包含对参数的引用,这些参数在该节点表示的表达式的上下文中定义。
  • ExpressionType.DebugInfo:调试信息。
  • ExpressionType.Dynamic:动态操作(注:dynamic关键字,这里不研究这个)。
  • ExpressionType.RuntimeVariables:运行时变量的列表。

2. 表达式树和lambda表达式

简单一句话:lambda表达式是表达式树的一个特例,表达式树常需要转换成lambda表达式。

2.1 lambda表达式的限制

当我们在使用IDE自动将委托转为lambda表达式时,我们的表达式树不能有块操作,不能有赋值操作,如下:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
但是,如果我们自己组装表达式树的话就没有这两个限制,通过前面列举的代码,我们发现表达式树几乎能模拟我们写的大部分代码。

咳咳,是几乎能模拟,因为它模拟的范围限制在了一个方法内,即:我们最多自己组装一个表达式树把它编译成方法,但我们无法依靠表达式树去定义类、接口、程序集等等。。。

3. 如何用表达式树构建一个递归方法

参考: https://cloud.tencent.com/developer/ask/sof/805778

这里遇到的问题是:我们希望方法能调用自己,但是方法本身还是个表达式树,尚未编译成一个方法,怎么能够调用自己呢?

解决问题的思路:找到一个委托类型的变量去记录这个即将被编译好的方法,然后在表达式树构建中调用这个委托变量,等编译完成后再把编译好的方法赋值给这个委托变量。说白了就是找个中间人过度一下。

看下面的示例:

/* 目标是实现下面的方法(斐波那契数列)
static int Fibonacci(int i)
{
    int res;
    if (i == 0) { res = 0; }
    else if (i == 1) { res = 1; }
    else res = Fibonacci(i - 2) + Fibonacci(i - 1);
    return res;
}
*/

//先定义一个中间类
public class Wrapper<T>
{
    public T Value { get; set; }
}

//正式方法
Wrapper<Func<int, int>> wrapper = new Wrapper<Func<int, int>>();

var para = Expression.Parameter(typeof(int), "i");
var localRes = Expression.Parameter(typeof(int), "res");
var conditional = Expression.IfThenElse(
      Expression.Equal(para, Expression.Constant(0)),
      Expression.Assign(localRes, Expression.Constant(0)),
      Expression.IfThenElse(
          Expression.Equal(para, Expression.Constant(1)),
          Expression.Assign(localRes, Expression.Constant(1)),
          Expression.Assign(
              localRes,
              Expression.Add(
                  Expression.Invoke(
                      Expression.Property(Expression.Constant(wrapper), "Value"),
                      Expression.Subtract(para, Expression.Constant(1))
                  ),
                  Expression.Invoke(
                      Expression.Property(Expression.Constant(wrapper), "Value"),
                      Expression.Subtract(para, Expression.Constant(2))
                  )
              )
          )
      )
  );
var block = Expression.Block(new ParameterExpression[] { localRes }, localRes, conditional, localRes);
var func = Expression.Lambda<Func<int, int>>(block, new[] { para }).Compile();
wrapper.Value = func;

//测试代码
for (int i = 0; i < 10; i++)
{
    Console.WriteLine($"{i} => {func(i)}");
}

在这里插入图片描述

如果,我们不想定义额外的类的话,我们也可以使用局部变量代替,如下:

//Wrapper<Func<int, int>> wrapper = new Wrapper<Func<int, int>>();

Func<int, int> tmp = null;
Expression<Func<int, int>> expr = i => tmp(i);

var para = Expression.Parameter(typeof(int), "i");
var localRes = Expression.Parameter(typeof(int), "res");
var conditional = Expression.IfThenElse(
      Expression.Equal(para, Expression.Constant(0)),
      Expression.Assign(localRes, Expression.Constant(0)),
      Expression.IfThenElse(
          Expression.Equal(para, Expression.Constant(1)),
          Expression.Assign(localRes, Expression.Constant(1)),
          Expression.Assign(
              localRes,
              Expression.Add(
                  //Expression.Invoke(
                  //    Expression.Property(Expression.Constant(wrapper), "Value"),
                  //    Expression.Subtract(para, Expression.Constant(1))
                  //),
                  //Expression.Invoke(
                  //    Expression.Property(Expression.Constant(wrapper), "Value"),
                  //    Expression.Subtract(para, Expression.Constant(2))
                  //)
                  Expression.Invoke(expr, Expression.Subtract(para, Expression.Constant(1))),
                  Expression.Invoke(expr, Expression.Subtract(para, Expression.Constant(2)))
              )
          )
      )
  );
var block = Expression.Block(new ParameterExpression[] { localRes }, localRes, conditional, localRes);
var func = Expression.Lambda<Func<int, int>>(block, new[] { para }).Compile();
//wrapper.Value = func;
tmp = func;

4. 表达式树的类继承体系

在这里插入图片描述

  • 6
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jackletter

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值