环境:
上一篇: 《c#:表达式树概念及应用场景(Expression)》
罗列表达式树类型及应用场景:
在c#中共有85 个类型的表达式树节点,如下: 《ExpressionType 枚举》

下面将一一罗列哥哥表达式类型的用法和场景(以lambda表达式为目标)
1. 常见表达式类型
1.1 + ,+= ,checked(+)
涉及到的表达式节点类型:
ExpressionType.Add :两个操作数相加;ExpressionType.AddChecked :两个操作数相加,并检查计算是否溢出;ExpressionType.AddAssign :两个数相加并将结果赋值给第一个数;ExpressionType.AddAssignChecked :两个数相加并将结果赋值给第一个数,并检查是否溢出;
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()();
x = int.MaxValue;
y = 1;
int z = x + y;
Expression<Func<int>> addChecked = () => checked(x + y);
Expression<Func<int>> addCheckedExp = Expression.Lambda<Func<int>>(Expression.AddChecked(Expression.Constant(x), Expression.Constant(y)));
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);
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 });
上面基本上演示了+ 号相关的用法,不过我们注意到Expression.AddAssign() 函数还有一个MethodInfo? method 和LambdaExpression? 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; }
}
}
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");
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);
Expression<Func<int, int, int>> andExp = (x, y) => x & y;
res = andExp.Compile()(0x01, 0x03);
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);
Expression<Func<bool, bool, bool>> boolExp = (x, y) => x & y;
res = boolExp.Compile()(true, 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);
Expression<Func<bool, bool, bool>> andAlsoExp = (x, y) => x && y;
res = andAlsoExp.Compile()(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);
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 这种运算。
Expression<Func<bool, bool>> notExp = x => !x;
res = notExp.Compile()(true);
var paraX = Expression.Parameter(typeof(bool), "x");
notExp = Expression.Lambda<Func<bool, bool>>(Expression.Not(paraX), new ParameterExpression[] { paraX });
res = notExp.Compile()(true);
Expression<Func<int, int, bool>> notEqualExp = (x, y) => x != y;
res = notEqualExp.Compile()(1, 1);
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);
Expression<Func<int, int, bool>> equalExp = (x, y) => x == y;
res = equalExp.Compile()(1, 1);
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);
1.9 关系运算 > ,>=
涉及到的表达式节点类型:
ExpressionType.GreaterThan :“大于”比较,如 (a > b)。ExpressionType.GreaterThanOrEqual :“大于或等于”比较,如 (a >= b)。
Expression<Func<int, int, bool>> greaterThan = (x, y) => x > y;
res = greaterThan.Compile()(5, 4);
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);
Expression<Func<int, int, bool>> greaterThanOrEqual = (x, y) => x >= y;
res = greaterThanOrEqual.Compile()(5, 5);
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 递增运算符: ++a ,a++
涉及到的表达式节点类型:
ExpressionType.PreIncrementAssign :一元前缀递增,如 (++a)。 应就地修改 a 对象。ExpressionType.PostIncrementAssign :一元后缀递增,如 (a++)。 应就地修改 a 对象。
var para59 = Expression.Parameter(typeof(int), "x");
var increPara = Expression.Lambda<Func<int, int>>(Expression.PreIncrementAssign(para59), new ParameterExpression[] { para59 });
res = increPara.Compile()(5);
var para60 = Expression.Parameter(typeof(int), "x");
var increPara2 = Expression.Lambda<Func<int, int>>(Expression.PostIncrementAssign(para60), new ParameterExpression[] { para60 });
res = increPara2.Compile()(5);
1.12 递减运算符:--a ,a--
涉及到的表达式节点类型:
ExpressionType.PreDecrementAssign :一元前缀递减,如 (–a)。 应就地修改 a 对象。ExpressionType.PostDecrementAssign :一元后缀递减,如 (a–)。 应就地修改 a 对象。
这个和上面的类似,不再列举。
1.13 按位或逻辑 XOR 运算:a^b ,a^=b
涉及到的表达式节点类型:
ExpressionType.ExclusiveOr :按位或逻辑 XOR 运算,如 C# 中的 (a ^ b) 和 Visual Basic 中的 (a Xor b)。ExpressionType.ExclusiveOrAssign :按位或逻辑 XOR 复合赋值运算,如 c # 中 的 (^ = b) 。
Expression<Func<int, int, int>> exclusiveOrExp = (x, y) => x ^ y;
res = exclusiveOrExp.Compile()(0x02, 0x11);
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);
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);
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 会被识别为减法),但我们知道表达式树允许有这种操作即可。
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.15 移位运算:a>>b ,a>>=b
涉及到的表达式节点类型:
ExpressionType.RightShift :按位右移运算,如 (a >> b)。ExpressionType.RightShiftAssign :按位右移复合赋值运算,如 (a >>= b)。
Expression<Func<int, int, int>> rightShiftExp = (x, y) => x >> y;
res = rightShiftExp.Compile()(2, 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);
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);
1.16 移位运算:a<<b ,a<<=b
涉及到的表达式节点类型:
ExpressionType.LeftShift :按位左移运算,如 (a << b)。ExpressionType.LeftShiftAssign :按位左移运算,如 (a << b)。
这个和上面的类似,不再列举。
1.17 算数求反:-a ,checked(-a)
涉及到的表达式节点类型:
ExpressionType.Negate :算术求反运算,如 (-a)。 不应就地修改 a 对象。ExpressionType.NegateChecked :算术求反运算,如 (-a),进行溢出检查。 不应就地修改 a 对象。
var num11 = 23;
Expression<Func<int, int>> negateExp = (x) => -x;
res = negateExp.Compile()(2);
var para103 = Expression.Parameter(typeof(int), "x");
negateExp = Expression.Lambda<Func<int, int>>(Expression.Negate(para103), new ParameterExpression[] { para103 });
res = negateExp.Compile()(2);
Expression<Func<int, int>> negateCheckedExp = (x) => checked(-x);
var para104 = Expression.Parameter(typeof(int), "x");
negateCheckedExp = Expression.Lambda<Func<int, int>>(Expression.NegateChecked(para104), new ParameterExpression[] { para104 });
1.18 一元加法:+a
涉及到的表达式节点类型:
ExpressionType.UnaryPlus :一元加法运算,如 (+a)。 预定义的一元加法运算的结果是操作数的值,但用户定义的实现可以产生特殊结果。
这个比较特殊,感觉写这个没啥意思,一般情况下确实这样,不过文档上说可以自定义实现运算逻辑还是可以尝试的(最开始Add 的method 参数),这里只列举简单情况。
Expression<Func<int, int>> unaryPlusExp = (x) => +x;
res = unaryPlusExp.Compile()(2);
var para105 = Expression.Parameter(typeof(int), "x");
unaryPlusExp = Expression.Lambda<Func<int, int>>(Expression.UnaryPlus(para105), new ParameterExpression[] { para105 });
res = unaryPlusExp.Compile()(2);
1.19 null 合并运算:a??b
涉及到的表达式节点类型:
ExpressionType.Coalesce :表示 null 合并运算的节点,如 C# 中的 (a ?? b)。
Expression<Func<int?, int>> coalesceExp = x => x ?? 0;
res = coalesceExp.Compile()(null);
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);
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);
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);
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)。
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, typeof(Math).GetMethod("Pow")), new ParameterExpression[] { para130, para131 });
res = power.Compile()(2, 4);
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);
1.22 条件值:IsFalse 、IsTrue
这两个比较特殊,我也不知道他们有什么用,直接在ide中也写不出来。
涉及到的表达式节点类型:
ExpressionType.IsTrue :true 条件值。ExpressionType.IsFalse :false 条件值。
var paraIsTrue = Expression.Parameter(typeof(bool), "i");
var isTrue = Expression.Lambda<Func<bool, bool>>(Expression.IsTrue(paraIsTrue), paraIsTrue);
res = isTrue.Compile()(true);
res = isTrue.Compile()(false);
var paraIsFale = Expression.Parameter(typeof(bool), "i");
var isFalse = Expression.Lambda<Func<bool, bool>>(Expression.IsFalse(paraIsFale), paraIsFale);
res = isFalse.Compile()(true);
res = isFalse.Compile()(false);
1.23 装箱或类型测试:obj as object ,obj is int
涉及到的表达式节点类型:
ExpressionType.TypeAs :显式引用或装箱转换,其中如果转换失败则提供 null,如 C# 中的 (obj as SampleType)。ExpressionType.TypeIs :类型测试,如 C# 中的 obj is SampleType。
Expression<Func<int, object>> typeAs = x => x as object;
res = typeAs.Compile()(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);
Expression<Func<object, bool>> typeIs = x => x is int;
res = typeIs.Compile()(1);
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);
1.24 确切类型测试:TypeEqual
涉及到的表达式节点类型:
ExpressionType.TypeEqual :确切类型测试。
这个和ExpressionType.TypeIs 的区别是TypeEqual 是严格校验类型是否相等,而TypeIs 是含继承或实现关系也可以。
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());
res = typeEq.Compile()(new ArgumentException());
res = typeEq.Compile()(new Exception());
1.25 反码运算:~a
涉及到的表达式节点类型:
ExpressionType.OnesComplement :二进制反码运算,如 C# 中的 (~a)。
Expression<Func<int, int>> onesComplementExp = x => ~~x;
res = onesComplementExp.Compile()(2);
var onesComplementPara = Expression.Parameter(typeof(int), "i");
onesComplementExp = Expression.Lambda<Func<int, int>>(Expression.OnesComplement(Expression.OnesComplement(onesComplementPara)), onesComplementPara);
res = onesComplementExp.Compile()(2);
1.26 转换数据类型:(int)byte 、checked((int)byte)
涉及到的表达式节点类型:
ExpressionType.Convert :二强制转换或转换操作,如 C# 中的 (SampleType)obj。对于数值转换,如果转换后的值对于目标类型来说太大,这不会引发异常。ExpressionType.ConvertChecked :强制转换或转换操作,如 C# 中的 (SampleType)obj。 对于数值转换,如果转换后的值与目标类型大小不符,则引发异常。
Expression<Func<double, int>> convert = x => (int)x;
res = convert.Compile()(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);
Expression<Func<object, Exception>> convert2 = x => (Exception)x;
res = convert2.Compile()(new ArgumentException());
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());
Expression<Func<int, byte>> convertCheck = x => checked((byte)x);
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()(120);
1.27 常量和默认值:()=>1 、default(int)
涉及到的表达式节点类型:
ExpressionType.Constant :一个常量值。ExpressionType.Default :默认值。
Expression<Func<int>> constant = () => 1;
res = constant.Compile()();
constant = Expression.Lambda<Func<int>>(Expression.Constant(1));
res = constant.Compile()();
Expression<Func<int>> d = () => default(int);
res = d.Compile()();
d = Expression.Lambda<Func<int>>(Expression.Default(typeof(int)));
res = d.Compile()();
1.28 赋值运算符:i=1
涉及到的表达式节点类型:
ExpressionType.Assign :赋值运算,如 (a = b)。
在lambda表达式中是不允许出现赋值运算的,所以我们不能依赖ide自动处理我们写的委托。
public class Model
{
public int Id { get; set; }
}
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 }。
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()();
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()();
Expression<Func<int[,,]>> newArrayBounds = () => new int[2, 3, 2];
res = newArrayBounds.Compile()();
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()();
Expression<Func<List<int>>> listInit = () => new List<int>()
{
1,2,3
};
res = listInit.Compile()();
listInit = () => new List<int>();
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.Length 、arr[1] 、arr[1]=2
涉及到的表达式节点类型:
ExpressionType.ArrayLength :获取一维数组长长度的运算,如 array.Length。ExpressionType.ArrayIndex :一维数组中的索引运算,如 C# 中的 array[index]。
Expression<Func<int[], int>> arrayLen = i => i.Length;
res = arrayLen.Compile()(new int[] { 1, 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 });
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] { "刘备", "关羽", "张飞" });
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的调用。
Func<string> func5 = () => "Hello";
Expression<Func<string>> invoke = () => func5();
res = invoke.Compile()();
Expression<Func<string>> funcExp = () => "Hello";
Expression<Func<string>> invoke2 = Expression.Lambda<Func<string>>(Expression.Invoke(funcExp));
res = invoke2.Compile()();
public class ModelCall
{
public int Id { get; set; }
public string Name { get; set; }
public string Show()
{
return $"id={Id},name={Name}";
}
}
var inst = new ModelCall() { Id = 1, Name = "小明" };
Expression<Func<string>> call = () => inst.Show();
res = call.Compile()();
call = Expression.Lambda<Func<string>>(Expression.Call(Expression.Constant(inst), typeof(ModelCall).GetMethod("Show")));
res = call.Compile()();
1.32 访问实例成员:model.Id
涉及到的表达式节点类型:
ExpressionType.MemberAccess :从字段或属性进行读取的运算,如 obj.SampleProperty。
var model9 = new Model() { Id = 9 };
Expression<Func<int>> memberAccess = () => model9.Id;
res = memberAccess.Compile()();
memberAccess = Expression.Lambda<Func<int>>(Expression.MakeMemberAccess(Expression.Constant(model9), typeof(Model).GetProperty("Id")));
res = memberAccess.Compile()();
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。
Expression<Func<int>> lambdaExp = () => 1;
lambdaExp = Expression.Lambda<Func<int>>(Expression.Constant(1));
1.34 块表达式:{}
涉及到的表达式节点类型:
ExpressionType.Block :表达式块。
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()();
1.35 方法参数和局部变量:function(int x){int y=0;}
涉及到的表达式节点类型:
ExpressionType.Parameter :对在表达式上下文中定义的参数或变量的引用。
注意: 在表达式树中,c#使用同一个节点类型同时表示方法参数和局部变量,这可能有点费解(因为方法参数和局部变量肯定不同)。
c#应该是这样考虑的: 它们的本质都是一个引用,不过是引用来源的位置不同罢了,一个是来自本身(局部变量),一个是来自调用者传递(方法参数)。
那么如何区分方法参数和局部变量呢? 既然它们本质相同,所以它们在创建上应该没多大差别(方法Expression.Parameter(...) 和Expression.Variable(...) 基本相同),有差别的是我们把它写入的位置!!!
下面演示如何定义方法参数和局部变量:
- 方法参数
var paraI = Expression.Parameter(typeof(int), "i");
Expression<Func<int, int>> DemoExp = Expression.Lambda<Func<int, int>>(
Expression.Block(
Expression.Add(paraI, Expression.Constant(1))
),
new ParameterExpression[] { paraI });
res = DemoExp.Compile()(0);
我们可以在调试中看到:  - 局部变量
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)),
Expression.Add(variI, Expression.Constant(1))
));
res = Demo2Exp.Compile()();
我们可以在调试中观察到: 
看了上面,我们应该知道怎样在表达式树中创建和区分方法参数和局部变量了吧。
关于Expression.Variable和Expression.Parameter:
上面说了,它们两个基本一致,这里就从源码看下:
  可以看到,它们除了参数是否传递引用(ref关键字)时的细微差别,本质都一样,都是创建了ParameterExpression实例。而我们方法中很少有使用ref 的,所以一般情况下,Expression.Parameter 和Expression.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, "小红" } }
};
Expression<Func<ModelIndex, int, string>> modelIndexExp = (modelIndex, index) => modelIndex[index];
res = modelIndexExp.Compile()(modelIndex, 1);
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);
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);
-
数组索引
Expression<Func<int[], int, int>> arrIndexExp = (arr, index) => arr[index];
res = arrIndexExp.Compile()(new int[] { 1, 2 }, 1);
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);
arrIndexExp = Expression.Lambda<Func<int[], int, int>>(Expression.Block(
Expression.Assign(arrIndexExp, Expression.Constant(5)),
arrIndexExp
), new[] { paraIntArr, paraIntIndex });
对于数组索引的访问还有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#中不推荐使用,但我们应该了解下,示例代码:
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");
}
下面就用表达式树模拟下上面的代码:
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 表示的是跳转,这是一个大类,往下细分还有:return 、break 、continue ,goto 四个小类,看下面定义:  这四个小类都用GotoExpression 表示,分别对应Expression.Return(),Expression.Break(),Expression.Continue(),Expression.Goto() 四个方法,如下:    
1.38 循环 for
涉及到的表达式节点类型:
ExpressionType.Loop :一个循环,例如 for 或 while。
注意,下面以for 循环举例,其他循环如do while 都类似:
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));
var loopExp = Expression.Loop(
Expression.Block(
Expression.IfThenElse(
Expression.LessThan(parai, Expression.Constant(5)),
Expression.Block(
Expression.Call(null, typeof(Console).GetMethod("WriteLine", new[] { typeof(int) }), parai)
, Expression.PostIncrementAssign(parai)
, Expression.Continue(continueLabel)
),
Expression.Goto(breakLabel))
), 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();
其中调试组装后的表达式数如下: 
1.39 switch
涉及到的表达式节点类型:
ExpressionType.Switch :多分支选择运算,如 C# 中的 switch。
var paraSwitch = Expression.Parameter(typeof(int), "i");
var switchExp = Expression.Switch(
paraSwitch
, Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("default"))
, Expression.SwitchCase(
Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("case 1")),
Expression.Constant(1))
, Expression.SwitchCase(
Expression.Call(null, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("case 2")),
Expression.Constant(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))
);
Expression<Action<int>> switchLambda = Expression.Lambda<Action<int>>(switchExp, paraSwitch);
var act = switchLambda.Compile();
act(1);
act(2);
act(3);
act(4);
act(5);
act(0);
1.40 try-catch-finally throw
涉及到的表达式节点类型:
ExpressionType.Throw :引发异常的运算,如引发新异常()。ExpressionType.Try :try-catch 表达式。
var para = Expression.Parameter(typeof(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")),
Expression.IfThen(
Expression.Equal(para, Expression.Constant(0)),
Expression.Throw(
Expression.New(
typeof(NotImplementedException).GetConstructor(new[] { typeof(string) }),
Expression.Constant("测试未实现!")))
),
Expression.IfThen(
Expression.Equal(para, Expression.Constant(1)),
Expression.Throw(
Expression.New(
typeof(ArgumentNullException).GetConstructor(new[] { typeof(string) }),
Expression.Constant("测试参数!"))))
),
Expression.Call(null,
typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }),
Expression.Constant("finally")
),
Expression.MakeCatchBlock(typeof(NotImplementedException), para2,
Expression.Call(null,
typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }),
Expression.Add(
Expression.Constant("测试未实现 catch"),
Expression.MakeMemberAccess(para2, typeof(NotImplementedException).GetProperty("Message")),
typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }))),
null),
Expression.MakeCatchBlock(typeof(ArgumentNullException), para3,
Expression.Call(null,
typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }),
Expression.Add(
Expression.Constant("测试参数 catch"),
Expression.MakeMemberAccess(para3, typeof(ArgumentNullException).GetProperty("Message")),
typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }))),
null)
);
var lambdaExp = Expression.Lambda<Action<int>>(tryExp, new[] { para });
lambdaExp.Compile()(0);
注意: 上面抛出异常除了使用的是Expression.Throw(...) 方法,还可以使用Expression.ReThrow() ,它们分别相当于:throw new Exception 和throw; ,两种抛出异常方式有很大的差别,可以参考:《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. 表达式的限制
当我们在使用IDE自动将委托转为表达式时,我们的表达式数不能有块操作,不能有赋值操作,如下:
   但是,如果我们自己组装表达式树的话就没有这两个限制,通过前面列举的代码,我们发现表达式树几乎能模拟我们写的大部分代码。
咳咳,是几乎能模拟,因为它模拟的范围限制在了一个方法内,即:我们最多自己组装一个表达式树把它编译成方法,但我们无法依靠表达式树去定义类、接口、程序及等等。。。
|