• asp.net core 排序过滤分页组件:sieve(2)表达式树的复习


    在Sieve组件中使用了很多关于表达式树的知识,但在我们日常的工作中写表达式树的机会是非常少的,至少在我的编程生涯中没怎么写过表达式树(可能也就是3,4次)。所以,为了能够看懂Sieve里面的源代码,我决定还是再重新回顾一下表达式树这个知识点。

    IEnumerable和IQueryable

    表达式树提供了一个将可执行代码转换成数据的方法.如果你要在执行代码之前修改或转换此代码,那么它是很有用的.有其是当你要将C#代码—-如LINQ查询表达式转换成其他代码在另一个程序—-如SQL数据库里操作它.

    说到表达式树就不能不提到两个接口,一个接口是IEnumerable和它的泛型等价接口IEnumerable<out T>(后者继承自前者),还有一个就是IQueryable和它的泛型等价接口IQueryable<out T>(后者从前者继承)。

    来看一下两个接口的定义:

        //
        // 摘要:
        //     Exposes the enumerator, which supports a simple iteration over a collection of
        //     a specified type.
        //
        // 类型参数:
        //   T:
        //     The type of objects to enumerate.
        public interface IEnumerable<out T> : IEnumerable
        {
            //
            // 摘要:
            //     Returns an enumerator that iterates through the collection.
            //
            // 返回结果:
            //     An enumerator that can be used to iterate through the collection.
            IEnumerator<T> GetEnumerator();
        }
      //
        // 摘要:
        //     Exposes an enumerator, which supports a simple iteration over a non-generic collection.
        public interface IEnumerable
        {
            //
            // 摘要:
            //     Returns an enumerator that iterates through a collection.
            //
            // 返回结果:
            //     An System.Collections.IEnumerator object that can be used to iterate through
            //     the collection.
            IEnumerator GetEnumerator();
        }
     //
        // 摘要:
        //     Provides functionality to evaluate queries against a specific data source wherein
        //     the type of the data is not specified.
        public interface IQueryable : IEnumerable
        {
            //
            // 摘要:
            //     Gets the type of the element(s) that are returned when the expression tree associated
            //     with this instance of System.Linq.IQueryable is executed.
            //
            // 返回结果:
            //     A System.Type that represents the type of the element(s) that are returned when
            //     the expression tree associated with this object is executed.
            Type ElementType { get; }
            //
            // 摘要:
            //     Gets the expression tree that is associated with the instance of System.Linq.IQueryable.
            //
            // 返回结果:
            //     The System.Linq.Expressions.Expression that is associated with this instance
            //     of System.Linq.IQueryable.
            Expression Expression { get; }
            //
            // 摘要:
            //     Gets the query provider that is associated with this data source.
            //
            // 返回结果:
            //     The System.Linq.IQueryProvider that is associated with this data source.
            IQueryProvider Provider { get; }
        }
        //
        // 摘要:
        //     Provides functionality to evaluate queries against a specific data source wherein
        //     the type of the data is known.
        //
        // 类型参数:
        //   T:
        //     The type of the data in the data source.
        public interface IQueryable<out T> : IEnumerable<T>, IEnumerable, IQueryable
        {
        }

    在IQueryable接口中有一个Expression类型的属性,这个属性表明IQueryable是有一个表达式树来和它关联的。

    撤这么多就是为了阐述一个知识,IEnumerable用于linq to object,在内存中操作数据时,我们使用的是IEnumerable接口,当Linq查询需要从远程数据库比如SqlServer服务器上面查找数据时,这时得用IQueryable接口,IQueryable接口所代表的查询会通过其Expression属性翻译成目标平台的SQL语句来执行查询并最终返回数据。从这个层面上来说,Expression是作为一个中间层来被不同的LINQ to SQL翻译成目标平台的语言(文本,sql)。扯的有点儿远,继续复习Expression的API。

    表达式树

    System.Linq.Expressions命名空间中包含了代表各种表达式的各个类。他们都继承自Expression。Expression主要包含了各种工厂方法,这些方法用于创建其他表达式的实例。然而,Expression表达式也包含了两个属性

    • Type属性代表表达式求值后的.NET类型,可把它视作一个返回类型。例如:
     Expression first = Expression.Parameter(typeof(string), "x");
     MemberExpression propertyEx = Expression.MakeMemberAccess(first, typeof(string).GetProperty("Length"));
     Console.WriteLine(propertyEx.Type);//返回int
    • NodeType属性返回的是所代表的表达式种类。它是ExpressionType的枚举成员。包括LessThan、Multiply和Invoke以及上面这个例子会返回一个MemberAccess类型的Type。

    Expression有许多派生类, 其中一些可能有多个不同的节点类型。 例如, BinaryExpression就代表了具有两个操作数的任意操作: 算术、逻辑、比较、数组索引,等等。 这正是 NodeType属性重要的地方,因为它能区分由相同的 类表示的不同种类的表达式。

    下面演示一个非常简单的表达式树的创建过程:

    ConstantExpression first = Expression.Constant(2);
    ConstantExpression second = Expression.Constant(3);
    BinaryExpression body = Expression.Add(first, second);
    Console.WriteLine(body.Type);//int
    Console.WriteLine(body.NodeType);//Add

    我们想要表达两个常量相加的一个表达式树,过程就是首先创建这两个常量表达式,然后根据这两个表达式创建一个相加表达式。逻辑结构如下图所示:

    将表达式树编译成委托

     LambdaExpression是Expression的子类之一,而Expression<TDelegate>又是LambdaExpression的子类,他们的继承结构如下:

    Expression和Expression<TDelegate>的区别就在于,泛型的Expression是以静态类型的方式(编译期就已经明确了表达式的类型)标识了它是什么类型的表达式,也就是说它确定了返回类型和参数。我们可以用Expression.Lambda来返回一个泛型Expression。那Expression<TDelegate>的意义何在呢?首先,LambdaExpression有一个Compile方法能够创建恰当类型的委托。Expression<TDelegate>也有一个同名的方法,但它静态类型化后返回的是TDelegate类型的委托。下面的代码演示了这个特性:

    ConstantExpression first = Expression.Constant(2);
    ConstantExpression second = Expression.Constant(3);
    BinaryExpression body = Expression.Add(first, second);
    Func<int> func = Expression.Lambda<Func<int>>(body).Compile();
    Console.WriteLine(func());//5

    为了要打印一个5耗费了大量的时间但这并不是表达式树的本意,我们在程序中编辑了一些逻辑块,然后编译器将这些逻辑块变成真正可以执行的东西,此外,Expression<TDelegate>也有省事儿的方法,-----它可以直接由lambda表达式来显示或者隐式的转换。比如:

    Expression<Func<string,int>> expression=it=>it.Length;

    Lambda表达式转换成表达式树

    Lambda表达式可以转换成委托,同样,他也可以转换成Expression<TDelegate>。

     Expression<Func<int>> expression = () => 5;
                Func<int> func = expression.Compile();
                Console.WriteLine(func());

    幸亏有了这样的转换,要不我们使用表达式会累死!!!但是他也有一些限制,只能转换单一的表达式。总之如果你的lambda在编译的过程中报错了你就会知道这些限制,这里就不一一列出了。下面演示一个更复杂的:

    Expression<Func<string, string, bool>> expression = (x, y) => x.StartsWith(y);
    var compiled = expression.Compile();
    Console.WriteLine(compiled("pangjainxin", "pang"));

    如果没有lambda表达式的隐式转换,那么我们得书写下面的代码来达到这个目的:

                //①首先构造方法调用的各个部件
                MethodInfo startWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
                var target = Expression.Parameter(typeof(string), "x");
                var methodArg = Expression.Parameter(typeof(string), "y");
                Expression[] methodArgs = new[] { methodArg };
    
                //②然后从以上部件创建CallExpression
                Expression call = Expression.Call(target, startWith, methodArgs);
    
                //③然后从CallExpression转换成lambda表达式
                var lambdaParameters = new[] { target, methodArg };
                var lambda = Expression.Lambda<Func<string, string, bool>>(call, lambdaParameters);
                var compiled = lambda.Compile();
                Console.WriteLine(compiled("pangjainxin", "pang"));//true
                Console.WriteLine(compiled("pangjianxin", "angjianxin"));//false

    看起来要比隐式的lambda转换麻烦多了,下图展示了这一过程:

  • 相关阅读:
    进程、线程、协程
    python垃圾回收机制
    python变量存储和深浅拷贝
    Linux常用命令
    二叉树四种遍历,节点个数,深度
    装饰器
    ArrayList、Vector
    集合、Collection、迭代器、List
    卖票
    关于Thread和Runnable
  • 原文地址:https://www.cnblogs.com/pangjianxin/p/10791710.html
Copyright © 2020-2023  润新知