环境:
- window11
- .net 6.0
- vs2022
罗列表达式树类型及应用场景:
在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? 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; }
}
}
//实际想达成的效果:"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 递增运算符: ++a
,a++
涉及到的表达式节点类型:
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 递减运算符:--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) 。
//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>>b
,a>>=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<<b
,a<<=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)。 预定义的一元加法运算的结果是操作数的值,但用户定义的实现可以产生特殊结果。
这个比较特殊,感觉写这个没啥意思,一般情况下确实这样,不过文档上说可以自定义实现运算逻辑还是可以尝试的(最开始Add
的method
参数),这里只列举简单情况。
//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 条件值:IsFalse
、IsTrue
这两个比较特殊,我也不知道他们有什么用,直接在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 object
,obj 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)byte
、checked((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 常量和默认值:()=>1
、default(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.Length
、arr[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.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, "小红" } } }; //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
表示的是跳转,这是一个大类,往下细分还有:return
、break
、continue
,goto
四个小类,看下面定义:
这四个小类都用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 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. 表达式树和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. 表达式树的类继承体系
5. 如何创建一个有返回值的简单的方法
有如下方法:
int show(int? p)
{
if (p == null) return 1;
return p.Value;
}
书写如下:
//方法入参声明: int? p
var para = Expression.Parameter(typeof(int?), "p");//参数名字也可以省略
//下面第一行创建一个 labelTarget, 然后其他的 goto 等跳转都可以朝这跳
//下面第二行创建了一个引用此 labelTarget 的 labelExpression,
// 这个 labelExpression 所在的位置, 就是所有指向 labelTarget 的 goto 语句的实际跳转位置
// 因为这里场景是返回, 所以后面会将 labelExpression 放在方法体的最后一行
var retLabelTarget = Expression.Label(typeof(int), "retLabel");//标签名字也可以省略
var retLabelExpr = Expression.Label(retLabelTarget, Expression.Constant(-1));//Expression.Constant(-1) 是默认值, 如果我们每次跳转都携带值, 这里也可不声明默认值
//if (p == null) goto retLabel 1; //解释: 如果 p==null 就跳转到 retLabel, 并携带常量值 1
var ifThen = Expression.IfThen(Expression.Equal(para, Expression.Constant(null)), Expression.Goto(retLabelTarget, Expression.Constant(1)));
var prop = typeof(int?).GetProperty("Value");
var pValue = Expression.Goto(retLabelTarget, Expression.MakeMemberAccess(para, prop));// goto retLabel p.Value
//最后组装, 注意: retLabelExpr 在最后一行
//.Lambda #Lambda1<System.Func`2[System.Nullable`1[System.Int32],System.Int32]>(System.Nullable`1[System.Int32] $p) {
// .Block() {
// .If($p == null) {
// .Goto retLabel { 1 }
// } .Else {
// .Default(System.Void)
// };
// .Goto retLabel { $p.Value };
// .Label
// - 1
// .LabelTarget retLabel:
// }
//}
var block = Expression.Block([ifThen, pValue, retLabelExpr]);
var lambda = Expression.Lambda<Func<int?, int>>(block, para);
var func = lambda.Compile();
int? p = null;
var v = func(p); //输出: 1
var v2 = func(2); //输出: 2
6. 如何创建一个无返回值的方法
有方法:
void show(string name)
{
Console.WriteLine("hello: " + name);
}
可以像上面那样写个返回值, 但返回值为 void, 也可以不写返回值
6.1 当不使用返回值控制的时候
var para = Expression.Parameter(typeof(string), "name");//参数名字也可以省略
var call = Expression.Call
(
typeof(Console).GetMethod("WriteLine", [typeof(string)]),
Expression.Add(
Expression.Constant("hello: "),
para,
typeof(string).GetMethod("Concat", [typeof(string), typeof(string)])
)
);
var block = Expression.Block([call]);
//.Lambda #Lambda1<System.Action`1[System.String]>(System.String $name) {
// .Block() {
// .Call System.Console.WriteLine("hello: " + $name)
// }
//}
var lambda = Expression.Lambda<Action<string>>(block, para);
var func = lambda.Compile();
func("jack"); //输出: hello: jack
6.2 当使用返回值控制时
//方法入参声明: string name
var para = Expression.Parameter(typeof(string), "name");//参数名字也可以省略
var retLabelTarget = Expression.Label(typeof(void), "retLabel");//标签名字也可以省略
var retLabelExpr = Expression.Label(retLabelTarget);
var call = Expression.Call
(
typeof(Console).GetMethod("WriteLine", [typeof(string)]),
Expression.Add(
Expression.Constant("hello: "),
para,
typeof(string).GetMethod("Concat", [typeof(string), typeof(string)])
)
);
var block = Expression.Block([call, Expression.Goto(retLabelTarget), retLabelExpr]);
//.Lambda #Lambda1<System.Action`1[System.String]>(System.String $name) {
// .Block() {
// .Call System.Console.WriteLine("hello: " + $name);
// .Goto retLabel { };
// .Label
// .LabelTarget retLabel:
// }
//}
var lambda = Expression.Lambda<Action<string>>(block, para);
var func = lambda.Compile();
func("jack"); //输出: hello: jack
7. 如何在方法内抛异常
有如下方法:
int show(int? p)
{
if (p == null) throw new Exception("xxxx");
return p.Value;
}
实现如下:
var para = Expression.Parameter(typeof(int?), "p");//参数名字也可以省略
//if (p == null) throw new Exception("xxxx");
var ifThen = Expression.IfThen
(
Expression.Equal(para, Expression.Constant(null)),
Expression.Throw(Expression.Constant(new Exception("xxxx")), typeof(int))
);// 注意抛异常的时候要指定 typeof(int) 因为方法最终返回是 int
var prop = typeof(int?).GetProperty("Value");
var block = Expression.Block([ifThen, Expression.MakeMemberAccess(para, prop)]);
//.Lambda #Lambda1<System.Func`2[System.Nullable`1[System.Int32],System.Int32]>(System.Nullable`1[System.Int32] $p) {
// .Block() {
// .If($p == null) {
// .Throw.Constant<System.Exception>(System.Exception: xxxx
// at lambda_method1(Closure, Nullable`1)
// at Program.< Main >$(String[] args) in D:\jackletter\codes\TestSln\ConsoleApp4\Demo.cs:line 42)
// } .Else {
// .Default(System.Void)
// };
// $p.Value
// }
//}
var lambda = Expression.Lambda<Func<int?, int>>(block, para);
var func = lambda.Compile();
int? p = 2;
var v = func(p); //输出: 2
p = null;
func(p); //抛出异常: System.Exception:“xxxx”