C# Expression
Expression简介
表达式树又称为“表达式目录树”,以数据形式表示语言级代码,它是一种抽象语法树或者说是一种数据结构。
简述一个Expression 表达式
1是一个节点,是一个常量类型的表达式,+号是一个节点,是一个二进制类型的表达式。在Expression中每个元素都是一个独立的节点,节点的拼接可以看似一个无线链接的树。
使用API方式创建一个表达式
//创建lambda表达式 num=>num>=5 //第一步创建输入参数,参数名为num,类型为int类型 ParameterExpression numParameter = Expression.Parameter(typeof(int), "num"); //第二步创建常量表达式5,类型int ConstantExpression constant = Expression.Constant(5, typeof(int)); //第三步创建比较运算符>=,大于等于,并将输入参数表达式和常量表达式输入 //表示包含二元运算符的表达式。BinaryExpression继承自Expression BinaryExpression greaterThanOrEqual = Expression.GreaterThanOrEqual(numParameter, constant); //第四步构建lambda表达式树 //Expression.Lambda<Func<int, bool>>:通过先构造一个委托类型来创建一个 LambdaExpression Expression<Func<int, bool>> lambda = Expression.Lambda<Func<int, bool>>(greaterThanOrEqual, numParameter);
整个表达式树节点的继承链
修改表达式树
现在我们想要修改表达式目录树,让它表示的Lambda表达式为(a,b)=>(a - (b * 2)),这时就需要编写自己的表达式目录树访问器,如下代码所示:
public class OperationsVisitor : ExpressionVisitor { public Expression Modify(Expression expression) { return Visit(expression); } protected override Expression VisitBinary(BinaryExpression b) { if (b.NodeType == ExpressionType.Add) { Expression left = this.Visit(b.Left); Expression right = this.Visit(b.Right); return Expression.Subtract(left,right); } return base.VisitBinary(b); } }
Linq之Expression
Expression最先接触的是使用Linq,使用Linq可以快速的创建一个简单的Expression
例如:
Func<int, int> func = num => num += 1;
这是一个数值加法的运算,输入一个数字进行+1之后返回。
经常在使用一个函数作为参数的时候我们会使用上述的方法,如果我想动态调整一个函数的时候,使用上述Func传递好像就行了,这个时候Expression就是我们最好的工具了。
Expression<Func<int, bool>> func2 = num2 => num2>= 1;
上述过程中使用lambda表达式创建一个最简单的LambdaExpression树。解析成树结构如下:
Expression<Func<int, int>> lambda = n => { int result = 0; for (int j = n; j >= 0; j--) { result += j; } return result; };
上述代码,使用lambda形式创建了一个执行循环的表达式树,目前C#还不支持这样创建,如果想创建复杂的Expression,还需要使用API创建。
Linq 之 IQueryProvider & IQueryable<TData>
使用Linq to sql、Beisen.Storage中使用Expression查询多租赁数据,也是使用 IQueryProvider 能力实现,IQueryProvider主要实现逻辑是使用Expression做为方法的参数,内部解析Expression转换为目标语言,如sql,webService,ESFilter等等。
IQueryProvider 源码
public interface IQueryProvider { // 创建Object类型的IQueryable IQueryable CreateQuery(Expression expression); // 创建强类型的IQueryable IQueryable<TElement> CreateQuery<TElement>(Expression expression); //执行返回Object类型的计算 object? Execute(Expression expression); //执行返回强类型的计算 TResult Execute<TResult>(Expression expression); }
IQueryable源码
public interface IQueryable : IEnumerable { //当前查询使用的表达式树 Expression Expression { get; } //当前循环数据类型 Type ElementType { get; } //执行查询服务 IQueryProvider Provider { get; } }
如果想让一个数据支持查询,那么必须实现IQueryable,上述源码中可以看到继承于IEnumerable,一个支持查询的数据接口,必须实现迭代器,也就是说明一个数据支持了Linq。
目前想要扩展Linq有两种方式,一个是使用IEnumerable,一个是使用IQueryable。
使用IEnumerable实现Linq
public class EnumerableData<T> : IEnumerable<T> where T : class { public IEnumerator<T> GetEnumerator() { return null; } IEnumerator IEnumerable.GetEnumerator() { return null; } // 其它成员 }
实现一个QueryProvider
public class DemoQueryProvider : IQueryProvider { public IQueryable CreateQuery(Expression expression) { Type elementType = expression.Type; try { return (IQueryable)Activator.CreateInstance( typeof(QueryableData<>).MakeGenericType(elementType), new object[] { this, expression }); } catch { throw new Exception(); } } public IQueryable<TResult> CreateQuery<TResult>(Expression expression) { return new QueryableData<TResult>(this, expression); } public object? Execute(Expression expression) { throw new NotImplementedException(); } public TResult Execute<TResult>(Expression expression) { if (expression is MethodCallExpression methodCallEx) { var listValue = (methodCallEx.Arguments[0] as ConstantExpression)?.Value as QueryableData<string>; var method = (methodCallEx.Arguments[1] as UnaryExpression)?.Operand; var lambdaEx = (method as Expression<Func<string,string>>)?.Compile(); return (TResult)Exec(listValue, lambdaEx); } if (expression is ConstantExpression { Value: TResult result }) { return result; } return default(TResult); } public IEnumerable<string> Exec(QueryableData<string> listValue,Func<string,string> lambdaEx) { //这里换成列表类型,为了防止循环使用QueryableData 的GetEnumerator,导致死循环 foreach (var item in listValue.ToList()) { yield return lambdaEx(item); } } }
public class QueryableData<TData> : List<TData>,IQueryable<TData> { public QueryableData() { Provider = new DemoQueryProvider(); Expression = Expression.Constant(this); } public QueryableData(DemoQueryProvider provider, Expression expression) { if (provider == null) { throw new ArgumentNullException("provider"); } if (expression == null) { throw new ArgumentNullException("expression"); } if (!typeof(IQueryable<TData>).IsAssignableFrom(expression.Type)) { throw new ArgumentOutOfRangeException("expression"); } Provider = provider; Expression = expression; } public IQueryProvider Provider { get; private set; } public Expression Expression { get; private set; } public Type ElementType { get { return typeof(TData); } } public IEnumerator<TData> GetEnumerator() { return (Provider.Execute<IEnumerable<TData>>(Expression)).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return (Provider.Execute<IEnumerable>(Expression)).GetEnumerator(); } }
Expression &Reflection.Emit
//创建lambda表达式 num=>num>=5 //第一步创建输入参数,参数名为num,类型为int类型 ParameterExpression numParameter = Expression.Parameter(typeof(int), "num"); //第二步创建常量表达式5,类型int ConstantExpression constant = Expression.Constant(5, typeof(int)); //第三步创建比较运算符>=,大于等于,并将输入参数表达式和常量表达式输入 //表示包含二元运算符的表达式。BinaryExpression继承自Expression BinaryExpression greaterThanOrEqual = Expression.GreaterThanOrEqual(numParameter, constant); //第四步构建lambda表达式树 //Expression.Lambda<Func<int, bool>>:通过先构造一个委托类型来创建一个 LambdaExpression Expression<Func<int, bool>> lambda = Expression.Lambda<Func<int, bool>>(greaterThanOrEqual, numParameter);
这是上述的一个Demo,使用ExpressionAPI手动创建了一个表达式树,如果想要执行这段程序,还需要编译一下,LambdaExpression独有的方法Compile,接下来看看Compile中做了什么。
public new TDelegate Compile() { #if FEATURE_COMPILE return (TDelegate)(object)Compiler.LambdaCompiler.Compile(this); #else return (TDelegate)(object)new Interpreter.LightCompiler().CompileTop(this).CreateDelegate(); #endif } internal sealed class LightDelegateCreator { private readonly LambdaExpression _lambda; internal LightDelegateCreator(System.Linq.Expressions.Interpreter.Interpreter interpreter, LambdaExpression lambda) { this.Interpreter = interpreter; this._lambda = lambda; } internal System.Linq.Expressions.Interpreter.Interpreter Interpreter { get; } public Delegate CreateDelegate() => this.CreateDelegate((IStrongBox[]) null); internal Delegate CreateDelegate(IStrongBox[] closure) => new LightLambda(this, closure).MakeDelegate(this._lambda.Type); } //LightLambda.MakeDelegate internal Delegate MakeDelegate(Type delegateType) => delegateType.GetInvokeMethod().ReturnType == typeof (void) ? DelegateHelpers.CreateObjectArrayDelegate(delegateType, new Func<object[], object>(this.RunVoid)) : DelegateHelpers.CreateObjectArrayDelegate(delegateType, new Func<object[], object>(this.Run)); //使用emit创建数组类型的委托,实际就是DynamicMethod internal static Delegate CreateObjectArrayDelegate(Type delegateType,Func<object[], object> handler) { return DelegateHelpers.CreateObjectArrayDelegateRefEmit(delegateType, handler); }
private static Delegate CreateObjectArrayDelegateRefEmit( Type delegateType, Func<object[], object> handler) { MethodInfo methodInfo; if (!DelegateHelpers.s_thunks.TryGetValue(delegateType, out methodInfo)) { MethodInfo invokeMethod = delegateType.GetInvokeMethod(); Type returnType = invokeMethod.ReturnType; bool hasReturnValue = returnType != typeof (void); ParameterInfo[] parametersCached = invokeMethod.GetParametersCached(); methodInfo = DelegateHelpers.GetCSharpThunk(returnType, hasReturnValue, parametersCached); if (methodInfo == (MethodInfo) null) { int num = Interlocked.Increment(ref DelegateHelpers.s_ThunksCreated); Type[] parameterTypes = new Type[parametersCached.Length + 1]; parameterTypes[0] = typeof (Func<object[], object>); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("Thunk"); stringBuilder.Append(num); if (hasReturnValue) { stringBuilder.Append("ret_"); stringBuilder.Append(returnType.Name); } for (int index = 0; index < parametersCached.Length; ++index) { stringBuilder.Append('_'); stringBuilder.Append(parametersCached[index].ParameterType.Name); parameterTypes[index + 1] = parametersCached[index].ParameterType; } DynamicMethod dynamicMethod = new DynamicMethod(stringBuilder.ToString(), returnType, parameterTypes); methodInfo = (MethodInfo) dynamicMethod; ILGenerator ilGenerator = dynamicMethod.GetILGenerator(); LocalBuilder local1 = ilGenerator.DeclareLocal(typeof (object[])); LocalBuilder local2 = ilGenerator.DeclareLocal(typeof (object)); if (parametersCached.Length == 0) { ilGenerator.Emit(OpCodes.Call, DelegateHelpers.s_ArrayEmpty); } else { ilGenerator.Emit(OpCodes.Ldc_I4, parametersCached.Length); ilGenerator.Emit(OpCodes.Newarr, typeof (object)); } ilGenerator.Emit(OpCodes.Stloc, local1); bool flag = false; for (int index = 0; index < parametersCached.Length; ++index) { bool isByRef = parametersCached[index].ParameterType.IsByRef; Type type = parametersCached[index].ParameterType; if (isByRef) type = type.GetElementType(); flag |= isByRef; ilGenerator.Emit(OpCodes.Ldloc, local1); ilGenerator.Emit(OpCodes.Ldc_I4, index); ilGenerator.Emit(OpCodes.Ldarg, index + 1); if (isByRef) ilGenerator.Emit(OpCodes.Ldobj, type); Type boxableType = DelegateHelpers.ConvertToBoxableType(type); ilGenerator.Emit(OpCodes.Box, boxableType); ilGenerator.Emit(OpCodes.Stelem_Ref); } if (flag) ilGenerator.BeginExceptionBlock(); ilGenerator.Emit(OpCodes.Ldarg_0); ilGenerator.Emit(OpCodes.Ldloc, local1); ilGenerator.Emit(OpCodes.Callvirt, DelegateHelpers.s_FuncInvoke); ilGenerator.Emit(OpCodes.Stloc, local2); if (flag) { ilGenerator.BeginFinallyBlock(); for (int index = 0; index < parametersCached.Length; ++index) { if (parametersCached[index].ParameterType.IsByRef) { Type elementType = parametersCached[index].ParameterType.GetElementType(); ilGenerator.Emit(OpCodes.Ldarg, index + 1); ilGenerator.Emit(OpCodes.Ldloc, local1); ilGenerator.Emit(OpCodes.Ldc_I4, index); ilGenerator.Emit(OpCodes.Ldelem_Ref); ilGenerator.Emit(OpCodes.Unbox_Any, elementType); ilGenerator.Emit(OpCodes.Stobj, elementType); } } ilGenerator.EndExceptionBlock(); } if (hasReturnValue) { ilGenerator.Emit(OpCodes.Ldloc, local2); ilGenerator.Emit(OpCodes.Unbox_Any, DelegateHelpers.ConvertToBoxableType(returnType)); } ilGenerator.Emit(OpCodes.Ret); } DelegateHelpers.s_thunks[delegateType] = methodInfo; } return methodInfo.CreateDelegate(delegateType, (object) handler); }
查看上述链路最后到方法CreateObjectArrayDelegateRefEmit,此方法中使用Emit创建一个叫DynamicMethod的类型。所以lambda中最终使用的是Emit方式将函数在运行时编译,且只编译一次。