zoukankan      html  css  js  c++  java
  • ASP.NET Core C# 反射 & 表达式树 (第三篇)

    前言

    前一篇讲完了反射, 这一篇来讲一下和反射息息相关的表达式树.

    首先搞清楚 Delegate, Action, Func, Anonymous Method, Lambda, Expression tree

    看大神的文章: C#中的Lambda表达式和表达式树

    简单说, Delegate 委托是上古年代的东西, 

    后来有了泛型, 就演化成 Action 和 Func

    然后开始玩匿名函数就有了 Lambda 表达式

    然后又出来了一个表达式树.

    Lambda 其实就是 Delegate, Action, Func 一样的东西. 

    而表达式树, 则是一些方法调用的表示手法.

    表达式树由很多表达式组成, 经过 compile 可以变成 Lambda, 然后拿去执行. 

    也可以 parse 这个表达式树, 翻译成其它的东西. 

    比如 EF Core,

    var person = await DbContext.People.Where(p => p.Name == "Derrick").ToListAsync();

    上面这一句它可以翻译成 SQL

    SELECT * FROM People WHERE Name = 'Derrick';

    这篇不会讲到如何做翻译, 只会讲到如何动态创建表达式树.

    Dynamic Expression How It Work

    模拟一下动态创建 Expression for call EF Core's SingleAsync 方法.

    public class Person {
        public string Name { get; set; } = "";
    }
    public class Program
    {
        public static void Main()
        {
            var people = new List<Person> {
                new Person { Name = "n1" },
                new Person { Name = "n2" },
                new Person { Name = "n3" },
                new Person { Name = "n4" },
            };
            var person = people.AsQueryable().Single(p => p.Name == "n1");
            // p => p.Name == "n1"
            var pExp = Expression.Parameter(typeof(Person), "p"); // p
            var pDotNameExp = Expression.Property(pExp, "Name"); // p.Name
            var valueExp = Expression.Constant("n1"); // "n1"
            var pDotNameEqualValueExp = Expression.Equal(pDotName, value); // p.Name == "n1"
            var lambda = (Expression<Func<Person, bool>>)Expression.Lambda(pDotNameEqualValueExp, new ParameterExpression[] { pExp }); // p => p.Name == "n1"
            var lambdaDelegate = lambda.Compile(); // 把表达式树 compile 成可执行的委托
            var person2 = people.AsQueryable().Single(lambda); // queryable 情况下参数是表达式, 因为要翻译成 SQL
            var person3 = people.Single(lambdaDelegate); // LINQ 的情况下参数是委托, 因为它是直接执行的
        }
    }

    需要动态创建的表达式是这一句 p => p.Name == "n1"

    从上面可以看到, 它是逐个逐个通过 Expression 创建和拼接出来的. 看注释一句一句理解. 

    你会发现它的语法有一种 left right 的感觉, 然后拼着拼着就有点像棵树了, 二叉树

    通常, 最后会把 Expression 变成 lambda 作为方法的参数, 需不需要 compile 就看最终拿来干什么. 如果是 Queryable 就直接给表达式树它翻译, 如果是 LINQ 就 compile 成委托执行.

    多一个例子

    var people = new List<Person> {
        new Person { Age = 1 },
        new Person { Age = 2 },
        new Person { Age = 3 },
        new Person { Age = 4 },
    };
    var person = people.AsQueryable().Where(p => p.Age >= 1 && p.Age <= 3);
    // p => p.Age >= 1 && p.Age <= 3
    var pExp = Expression.Parameter(typeof(Person), "p"); // p
    var pDotAgeExp = Expression.Property(pExp, "Age"); // p.Age
    var value1Exp = Expression.Constant(1); // 1
    var pDotAgeGreatThanOrEqualvalue1Exp = Expression.GreaterThanOrEqual(pDotAgeExp, value1Exp); // p.Age >= 1
    var value3Exp = Expression.Constant(3); // 3
    var pDotAgeLessThanOrEqualvalue3Exp = Expression.LessThanOrEqual(pDotAgeExp, value3Exp); // p.Age <= 1
    var and = Expression.And(pDotAgeGreatThanOrEqualvalue1Exp, pDotAgeLessThanOrEqualvalue3Exp); // p.Age >= 1 && p.Age <= 3
    var lambda = (Expression<Func<Person, bool>>)Expression.Lambda(and, pExp); // p => p.Age >= 1 && p.Age <= 3
    var lambdaDelegate = lambda.Compile();
    var result = people.Where(lambdaDelegate).ToList(); // [1,2,3]

    不管表达式如何复杂, 它就是逐个逐个去拼接, 所以不要害怕. Expression 里面有非常非常都多方法, 几乎你能写出来的 code 都能找到对应的 Expression

    Expression.Call

    表达式中也可以表达方法调用哦. 当然这个对翻译 SQL 的话可能会有点问题啦. 但是 for 执行的话就很好用哦. 

    public class Person {
        public int Age { get; set; }
        public bool IsDeleted(int value) 
        {
            return true;
        }
    }
    public class Program
    {
        public static void Main()
        {
            var people = new List<Person> {
                new Person { Age = 1 },
                new Person { Age = 2 },
                new Person { Age = 3 },
                new Person { Age = 4 },
            };
            // p => p.IsDeleted(5)
            var pExp = Expression.Parameter(typeof(Person), "p"); // p
            var method = typeof(Person).GetMethod("IsDeleted")!; // 通过反射获取 MethodInfo
            var callMethodExp = Expression.Call(pExp, method, new Expression[] { Expression.Constant(5) }); // p.IsDeleted(5)
            var lambda = (Expression<Func<Person, bool>>)Expression.Lambda(callMethodExp, pExp); // p => p.IsDeleted(5)
            var lambdaDelegate = lambda.Compile();
            var result = people.Where(lambdaDelegate).ToList(); // [1,2,3,4]
        }
    }

    总结

    动态创建表达式树, 可以帮助我们更好的管理 EF.Core 的代码. OData 也是通过这个方式去写入 EF Core 的 OData -> EF Core -> SQL.

    以前我不喜欢写反射, 表达式树, 但后来我发现, 只要把小方法写好, 一点一点拼接上来, 它只是代码多, 但是并不会乱. 所以不要怕.

  • 相关阅读:
    用户数据报协议---UDP
    斐波那契数列
    从尾到头打印链表
    Mybatis三种查询方式
    Mybatis配置
    字典的用法
    遍历列表、切片、定义元组
    与列表相关知识
    python一些方法总结
    计算机的容量
  • 原文地址:https://www.cnblogs.com/keatkeat/p/15501885.html
Copyright © 2011-2022 走看看