.NET Core表達式樹的梳理


最近要重寫公司自己開發的ORM框架;其中有一部分就是查詢的動態表達式;於是對這方面的東西做了一個簡單的梳理

官網的解釋:

表達式樹以樹形數據結構表示代碼,其中每一個節點都是一種表達式,比如方法調用和 x < y 這樣的二元運算等。

你可以對表達式樹中的代碼進行編輯和運算。 這樣能夠動態修改可執行代碼、在不同數據庫中執行 LINQ 查詢以及創建動態查詢。

表達式樹還能用於動態語言運行時 (DLR) 以提供動態語言和 .NET 之間的互操作性,同時保證編譯器編寫員能夠發射表達式樹而非 Microsoft 中間語言 (MSIL)

 

總結:構造可執行的代碼,以樹形結構的方式構造;

表達式與委托:委托可以直接執行,而表達式不可以,表達式可以編譯成委托

備注:實際上.NET還有以文本的方式構造可執行代碼塊的方式,但和表達式樹還是有區別

 

我個人工作中主要在以下場景中使用表達式樹:

1,代理類方法的構造(如AOP,gRpc,WebService中),通過表達式樹,構造委托執行要代理的方法

2,EF,MongoDB,自定義ORM等數據訪問層過濾使用的Expression,最典型的就是針對IQueryable類型分頁,排序的動態擴展

3,常用的Linq中AND,OR等Predicate加強的一些擴展

4,數學表達式,偽SQL代碼的執行解析器的構造(這個比較底層)

 

Expression調試工具:2019下載地址ExpressionTreeVisualizer

Expression調試工具源碼https://github.com/zspitz/ExpressionTreeVisualizer/blob/master/README.md

Expression類有以下屬性:

Body:表達式的主體(類似於方法體)

Parameters:Lambda表達示的參數(參數列表)

NodeType:得到樹中某些節點的表達式類型(ExpressionType),這是一個有45種不同值的枚舉類型,代表表達式節點的所有可能類型,如返回常數、
也可能返回參數、或者返回一個值是否小於另外一個(<),或者返回一個值是否大於另外一個(>),或者返回兩個值的和( )等等。

Type:得到表達式的靜態類型

 

表達式樹的幾個比較常用的類型/方法,.NET FrameWork與.NET Core在表達式樹的使用上做了一些調整

Expression:表達式樹核心類,基類;提供了Call,Add等構建Expression的靜態方法

Expression.Call():非常核心的一個方法,用於創建調用類的方法的方法類型表達式樹;在.NET Core(易用)和.NET FrameWork(易懂)做了些變化

Expression.Parameter():用於創建參數類型表達式樹

Expression.Constant():用於創建常量類型表達式樹

Expression<TDelegate>:強類型的表達式樹類

Expression.Lambda<TDelegate>:將Expression轉換成Lambda表達式(設置Lambda的方法體和參數列表)

LambdaExpression.Parameters:做表達式樹拼接時常用到

LambdaExpression.tailCall:屬性,標記是否【尾調優化,參考后面

 

創建表達式樹的方式(以下示例為.Net Core3.1)

1,以單行Lambda表達式創建表達式樹( 包含方法體的Lambda表達式是不能創建表達式樹 );

這種表達式樹的構造方式,Lambda表達式實際上是給LambdaExpression的Body(Body為Expression類型)賦值

        //使用單行Lambda創建表達式
        static void Expression1()
        {
            //表達式樹的Body
            Expression<Func<int, int>> expression = a => a + 100;
            Expression<Func<int, int, int>> expression2 = (a, b) => a + b;
            Console.WriteLine(expression.ToString());
            Console.WriteLine(expression.Compile()(120));
        }

如果你使用具有方法體的Lambda表達式創建表達式樹,是不成功的;表達式樹是Lambda表達式的內存中表現形式

        //錯誤示例:使用具有方法體的Lambda創建表達式
        static void Expression2()
        {
            //編譯不通過,不支持有方法體的Lambda創建表達式
            //Expression<Action<int>> expression1 = a => { };
            //編譯不通過,有方法體的Lambda創建表達式
            //Expression<Func<int, int>> expression2 = a => { return a + 100; };
            //Console.WriteLine(expression1.ToString());
        }

 

2,通過API創建表達式樹

    創建沒有入參和返回參數的表達式樹

        //無入參,返參的表達式
        static void Expression3()
        {
            var WriteLine = typeof(Console).GetMethod("WriteLine", new[] { typeof(string) });
            //表達式樹的Body
            var method = Expression.Block(Expression.Call(null, WriteLine, Expression.Constant("來自Expression的輸出")));
            var action = Expression.Lambda<Action>(method).Compile();
            action();
        }

  創建有入參,沒有返回參數的表達式樹

        //有入參,無返參的表達式
        static void Expression4()
        {
            var stringParam = Expression.Parameter(typeof(string), "stringParam");
            var WriteLine = typeof(Console).GetMethod("WriteLine", new[] { typeof(string) });
            var method = Expression.Block(Expression.Call(null, WriteLine, new[] { stringParam }));
            var action = Expression.Lambda<Action<string>>(method, new[] { stringParam }).Compile();
            action("來自Expression4的輸出yyyyyy");
        }

  創建有入參,有返回參數的表達式樹  

        //有入參,有返參的表達式1
        static void Expression5()
        {
            var stringParam = Expression.Parameter(typeof(string), "stringParam");
            var WriteLine = typeof(Console).GetMethod("WriteLine", new[] { typeof(string) });
            var method = Expression.Block(
                Expression.Call(null, WriteLine, new[] { stringParam }),
                Expression.Assign(stringParam, Expression.Constant("你好啊"))
                );
            var action = Expression.Lambda<Func<string, string>>(method, new[] { stringParam }).Compile();
            var ms = action("Expression5ss");
        }

        //有入參,有返參的表達式2
        static void Expression6()
        {
            var stringParam = Expression.Parameter(typeof(string), "stringParam");
            var method = Expression.Block(Expression.Call(null,typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }), new Expression[] { stringParam, Expression.Constant("哈哈哈哈") }));
            var action = Expression.Lambda<Func<string, string>>(method, new[] { stringParam }).Compile();
            var ms = action("test單個參數");
            Console.WriteLine(ms);
        }

        //有入參,有返參的表達式3
        static void Expression7()
        {
            var stringParam = Expression.Parameter(typeof(string), "stringParam");
            var stringParam2 = Expression.Parameter(typeof(string), "stringParam2");
            var method = Expression.Block(
                new[] { stringParam2 },
                Expression.Assign(stringParam2, Expression.Constant("參數2")),
                Expression.Call(null, typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }), new Expression[] { stringParam, stringParam2 })
                );
            var action = Expression.Lambda<Func<string, string>>(method, new[] { stringParam }).Compile();
            var ms = action("test單個參數,且Call聲明參數");
            Console.WriteLine(ms);
        }

        //有入參,有返參的表達式4
        static void Expression8()
        {
            var stringParam = Expression.Parameter(typeof(string), "stringParam");
            var stringParam2 = Expression.Parameter(typeof(string), "stringParam2");
            var method = Expression.Block(
                Expression.Call(null, typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }), new Expression[] { stringParam, stringParam2 })
                );
            var action = Expression.Lambda<Func<string, string,string>>(method, new[] { stringParam, stringParam2 }).Compile();
            var ms = action("多個參數:參數1","啦啦啦啦");
            Console.WriteLine(ms);
        }

 

函數尾部調用參數優化:簡稱尾調優化

1,當函數的最后一步時調用其他函數時,即為尾調;尾調不只是說函數的最尾部;示例,Update,Add方法都是尾部調用

public int AddUser(string id,string name) {
    if (id==null) {
       return Update(name);
    }
    return Add(name);
}

  

2,LambdaExpression.tailCall屬性和Expresssion.Lambda方法中的tailCall參數都是用來標記是否需要尾調優化的,

  如果標記尾調優化,則表示當執行進入尾調的函數時,當前函數已經不再依賴父函數上的數據,父函數上的所占用的內存空間將被清空,用於優化節省內存

 

表達式樹動態復制對象:

    /// <summary>
    /// 當前類
    /// </summary>
    public class Student
    {
        public int Id { get; set; }
        public string UserName { get; set; }
        public string Name { get; set; }
    }

    /// <summary>
    /// 目標類
    /// </summary>
    public class StudentCopy
    {
        public int Id { get; set; }
        public string UserName { get; set; }
        public string Name { get; set; }
    }

 

    class Program
    {
        static void Main(string[] args)
        {
            Student student = new Student()
            {
                Id = 1002,
                Name = "哈哈哈",
                UserName = "cc"
            };

            //使用new
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            var entity1 = CopyToByNew(student);
            stopwatch.Stop();
            Console.WriteLine($"new:{stopwatch.ElapsedTicks}");

            //使用反射
            stopwatch = new Stopwatch();
            stopwatch.Start();
            var entity2 = CopyToByReflect1<Student, StudentCopy>(student);
            stopwatch.Stop();
            Console.WriteLine($"reflect:{stopwatch.ElapsedTicks}");

            stopwatch = new Stopwatch();
            stopwatch.Start();
            var entity3 = CopyToByReflect2<Student, StudentCopy>(student);
            stopwatch.Stop();
            Console.WriteLine($"reflect:{stopwatch.ElapsedTicks}");

            //使用json
            stopwatch = new Stopwatch();
            stopwatch.Start();
            var entity4 = CopyToByJson<Student, StudentCopy>(student);
            stopwatch.Stop();
            Console.WriteLine($"json:{stopwatch.ElapsedTicks}");

            //使用表達式構造委托
            stopwatch = new Stopwatch();
            stopwatch.Start();
            var entity5 = CopyToByExpression<Student, StudentCopy>(student);
            stopwatch.Stop();
            Console.WriteLine($"expression:{stopwatch.ElapsedTicks}");

            Console.WriteLine("Hello World!");
            Console.ReadKey();
        }

        static StudentCopy CopyToByNew(Student student)
        {
            return new StudentCopy
            {
                Id = student.Id,
                Name = student.Name,
                UserName = student.UserName
            };
        }

        static Tout CopyToByReflect1<Tin, Tout>(Tin dto)
        {
            Tout entity = Activator.CreateInstance<Tout>();
            foreach (var itemOut in entity.GetType().GetProperties())
            {
                var propIn = dto.GetType().GetProperty(itemOut.Name);
                if (propIn != null)
                {
                    itemOut.SetValue(entity, propIn.GetValue(dto));
                }
            }
            return entity;
        }

        static Tout CopyToByReflect2<Tin, Tout>(Tin dto)
        {
            Tout entity = Activator.CreateInstance<Tout>();
            foreach (var itemOut in entity.GetType().GetFields())
            {
                var propIn = dto.GetType().GetField(itemOut.Name);
                itemOut.SetValue(entity, propIn.GetValue(dto));
            }
            return entity;
        }

        static Tout CopyToByJson<TIn, Tout>(TIn dto)
        {
            return JsonConvert.DeserializeObject<Tout>(JsonConvert.SerializeObject(dto));
        }

        static Tout CopyToByExpression<Tin, Tout>(Tin dto)
        {
            ParameterExpression parameterExpression = Expression.Parameter(typeof(Tin), "a");
            List<MemberBinding> memberBindings = new List<MemberBinding>();
            foreach (var item in typeof(Tout).GetProperties())
            {
                MemberExpression property = Expression.Property(parameterExpression, typeof(Tin).GetProperty(item.Name));
                MemberBinding memberBinding = Expression.Bind(item, property);
                memberBindings.Add(memberBinding);
            }
            MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(Tout)), memberBindings.ToArray());
            Expression<Func<Tin, Tout>> lambda = Expression.Lambda<Func<Tin, Tout>>(memberInitExpression,
                new ParameterExpression[] { parameterExpression });
            Func<Tin, Tout> func = lambda.Compile();
            return func(dto);
        }
    }

  

 

 

使用ExpressionTreeVisualizer工具

下載地址:參考前面的地址

安裝方式:https://docs.microsoft.com/en-us/visualstudio/debugger/how-to-install-a-visualizer?view=vs-2019#to-install-a-visualizer-for-visual-studio-2019

ExpressionTreeVisualizer工具主要有三個功能

  1. 表達式樹結構的可視化
  2. 表達式樹源碼顯示
  3. 表達式樹節點結構: parameters, closure variables, constants and default values


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM