05.表達式目錄樹Expression

參考文章html

1. 基本瞭解

1.1 Lambda表達式

演變過程node

using System;

namespace lq1
{
    class Program
    {
        public delegate void Tesk(int x);
        public delegate int TeskPara(int x);
        static void Main(string[] args)
        {
            new Program().Run();
        }

        public void Show(int x)
        {
            Console.WriteLine("Show");
        }
        public void Run()
        {
            // Lambda演變歷史
            {
                // .net framework 1.0/1.1
                Tesk tesk = new Tesk(this.Show);
                tesk.Invoke(1);
            }
            int i = 0;
            {
                // .net framework 2.0,匿名方法,增長delegate關鍵字,能夠訪問局部變量
                Tesk tesk = new Tesk(delegate (int x)
                {
                    Console.WriteLine("Show" + i);
                });
                tesk.Invoke(2);
            }
            {
                // .net framework 3.0 移除delegate關鍵字,增長 => 語法(goes to)
                Tesk tesk = new Tesk((int x) =>
                {
                    Console.WriteLine("Show" + i);
                });
                tesk.Invoke(3);
            }
            {
                // 能夠省略參數類型,參數類型根據委託自動推斷
                Tesk tesk = new Tesk((x) =>
                {
                    Console.WriteLine("Show" + i);
                });
                tesk.Invoke(3);
            }
            {
                // 只有一個參數或一行代碼時省略括號
                Tesk tesk = new Tesk(x => Console.WriteLine("Show" + i));
                tesk.Invoke(3);
            }
            {
                // 省略實例委託代碼
                Tesk tesk = x => Console.WriteLine("Show" + i);
                tesk.Invoke(3);
            }
            {
                // 有返回值且有一行代碼時,能夠直接寫返回值(省略 return)
                TeskPara tesk = x => x + 1;
                tesk.Invoke(5);

                Func<int, int> func = x => x + 2;
                func.Invoke(5);
            }
        }
    }
}

概述說明sql

Lambda表達式是一個特殊的匿名函數,是一種高效的相似於函數式編程的表達式,簡化開發中須要編寫的代碼量數據庫

能夠包含表達式和語句,而且可用於建立委託或表達式目錄樹類型,支持帶有可綁定到委託或表達式樹的輸入參數的內聯表達式express

全部Lambda表達式都使用Lambda運算符=>,運算符的左邊是輸入參數(若是有),右邊是表達式或語句塊編程

Lambda表達式不能在委託鏈中進行刪(-=)操做,由於每一個表達式的名稱都不同c#

1.2 隱式類型

在C# 3.0中,引進了關鍵字叫作varvar容許聲明一個新變量,它的類型是從用來初始化器變量的表達式裏隱式的推斷出來的,即在聲明時,不須要給它定義類型,它會根據它的初始化器表達式來推斷出它的類型數組

var 自己不是類型,而是向編譯器發出一條用來推斷和分配類型的指令,所以,隱式類型也叫推斷類型,由編輯器自動根據表達式推斷出對象的最終類型緩存

隱式類型的本地變量是強類型變量(就好像您已經聲明該類型同樣),但由編譯器肯定類型數據結構

var Name = "李白";
var Age = 18;
var Interest = new List<string>{"唱歌","閱讀"}

1.3 匿名類型

概述說明

字面意思:沒有名字的類型

演變歷史

public void Starting()
{
    {
        // 使用 object 聲明
        object model = new
        {
            id = 1,
            name = "libai"
        };
        //Console.WriteLine(model.id);  強類型語言,編譯器檢測不到object類型中有id屬性
    }
    {
        // 使用 dynamic 聲明,動態類型,避開編譯器檢查
        dynamic model = new
        {
            id = 2,
            name = "libai"
        };
        Console.WriteLine(model.id);
        Console.WriteLine(model.age);   // 運行時出錯
    }
    {
        // 使用 var 聲明,匿名類型,自動推斷,聲明後的屬性是隻讀的
        var model = new
        {
            id = 3,
            name = "libai"
        };
        Console.WriteLine(model.id);
        //Console.WriteLine(model.age); 編譯器檢測(自動推斷)無age字段
    }
}

1.4 擴展方法

概述說明

擴展方法:容許在不修改類型的內部代碼的狀況下爲類型添加獨立的行爲

擴展方法只能定義在 非泛型的靜態類中,使用 static修飾,參數使用this關鍵字 修飾要擴展的類。就是說擴展方法的第一個參數必須是this關鍵開頭而後經跟要擴展的對象類型,而後是擴展對象在運行時的實例對象引用

擴展方法是一種特殊的靜態方法,能夠像擴展類型上的實例方法同樣進行調用,能向現有類型「添加」方法,而無須建立新的派生類型、從新編譯或以其餘方式修改原始類型

在使用時編譯器認爲一個表達式要使用一個實例方法,可是沒有找到,須要檢查導入的命名空間和當前命名空間裏全部的擴展方法,並匹配到適合的方法

示例一:簡單定義

public static class Extend
{
    public static int ToInt(this int? k)
    {
        return k ?? 0;
    }
}

示例二:簡單定義

public interface ILog
{
    void log(string message,LogLevel logLevel);
}

public static class ILogExtensions
{
    //記錄調試信息
    public static void LogDebug(this ILog logger,string message)
    {
        if(true) //判斷日誌配置中是否容許輸入Debug類型的日誌
        {
            logger?.Log($"{message}",LogLevel.Debug);
        }
    }
}

示例三:模擬 where 方法

using System;
using System.Collections.Generic;

namespace lq2
{
    class Program
    {
        static void Main(string[] args)
        {
            List<User> list = new List<User>
            {
                new User(){uid=1,uname="a1",age=18,gender=0 },
                new User(){uid=2,uname="a2",age=28,gender=1 },
                new User(){uid=3,uname="a3",age=23,gender=1 },
                new User(){uid=4,uname="a4",age=18,gender=0 },
                new User(){uid=5,uname="a5",age=33,gender=1 }
            };

            var d1 = list.MyWhere(x => x.uid > 3);
            Console.WriteLine(d1.Count);

            var d2 = list.MyWhere(x => x.age >= 18 && x.gender == 0);
            Console.WriteLine(d2.Count);
        }
    }

    public class User
    {
        public int uid { get; set; }
        public int age { get; set; }
        public string uname { get; set; }
        public int gender { get; set; }
    }

    public static class Extend
    {
        public static List<T> MyWhere<T>(this List<T> rouse, Func<T, bool> func)
        {
            List<T> list = new List<T>();

            foreach (var item in rouse)
            {
                if (func(item))
                {
                    list.Add(item);
                }
            }
            return list;
        }
    }
}

2. 表達式目錄樹Expression

2.1 簡單概述

表達式樹,ExpressionSystem.Linq.Expressions),是一種數據結構體,用於存儲須要計算,運算的一種結構,這種結構能夠只是存儲,而不進行運算,或者說是描述不一樣變量和經常使用之間關係的一種數據結構

表達式目錄樹以數據形式表示語言級別代碼,數據存儲在樹形結構中,目錄樹中的每一個節點都表示一個表達式,簡單的說是一種語法樹,或者說是一種數據結構

表達式目錄樹不能有語句體,不能看成方法,不能有大括號,只能有一行代碼

2.2 聲明表達式目錄樹

第一種方式,快捷聲明,用Lambda聲明表達式目錄樹

示例一:普通類型

Expression<Func<int, int, int>> exp = (n, m) => n * m + 2;

示例二:實體類聲明

Expression<Func<User, bool>> lambda = x => x.age > 18;

第二種方式,手動拼裝目錄樹(原始方式),簡單示例

namespace e1
{
    using System;
    using System.Linq.Expressions;
    class Program
    {
        static void Main(string[] args)
        {
            // 以此表達式爲例,手動拼接,實現相同做用
            Expression<Func<int, int, int>> func = (x, y) => x * y + 2;

            // 聲明變量表達式
            ParameterExpression px = Expression.Parameter(typeof(int), "x");
            ParameterExpression py = Expression.Parameter(typeof(int), "y");

            // 聲明常量表達式
            ConstantExpression constant = Expression.Constant(2, typeof(int));

            // 聲明乘積表達式
            BinaryExpression multiply = Expression.Multiply(px, py);

            // 聲明相加表達式
            BinaryExpression add = Expression.Add(multiply, constant);

            // 聲明參數表達式
            ParameterExpression[] parameters = new ParameterExpression[] { px, py };

            // 生成表達式目錄樹
            Expression<Func<int, int, int>> exp = Expression.Lambda<Func<int, int, int>>(add, parameters);

            // 表達式目錄樹生成委託
            var ifunc = exp.Compile();

            Console.WriteLine("委託結果:" + func.Compile().Invoke(1, 2));
            Console.WriteLine("表達式樹:" + ifunc.Invoke(1, 2));
        }
    }
}

2.3 表達式類型

方法 類型 描述
Expression.Parameter(...) ParameterExpression 表示一個命名參數(變量)表達式
Expression.Constant(...) ConstantExpression 表示具備常量值的表達式
Expression.Add(...) BinaryExpression 表示具備(+,-,*,/)運算的表達式
Expression.Property/Field(...) MemberExpression 表示訪問屬性或字段
Expression.Call(...) MethodCallExpression 表示對靜態方法或實例方法的調用
Expression.Condition(...) ConditionalExpression 表示包含條件運算符的表達式
LambdaExpression 描述一個Lambda表達式
ListInitExpression 表示包含集合初始值設定項的構造函數調用
NewExpression 表示構造函數調用
NewArrayExpression 表示建立新數組並可能初始化改數組的元素
MemberMemberBinding 表示初始化新建立對象的成員的成員
MemberInitExpression 表示調用構造函數並初始化新對象的一個或多個成員
MemberAssignment 表示初始化新建立對象的字段或屬性
InvocationExpression 表示將委託或Lambda表達式應用於參數表達式列表的表達式
TypeBinaryExpression 表示表達式和類型之間的操做
UnaryExpression 表示包含一元運算符的表達式

3. 拼裝Expression

3.1 變量,常量拼裝

示例一:常量

static void Test1()
{
    // lambda方式
    Expression<Func<int>> func = () => 1 + 2;

    // 聲明常量表達式
    ConstantExpression constant1 =  Expression.Constant(1, typeof(int));
    ConstantExpression constant2 =  Expression.Constant(2, typeof(int));

    // 聲明相加表達式
    BinaryExpression add = Expression.Add(constant1, constant2);

    // 生成表達式目錄樹
    Expression<Func<int>> exp = Expression.Lambda<Func<int>>(add);

    // 表達式目錄樹生成委託
    var ifunc = exp.Compile();

    Console.WriteLine(func.Compile().Invoke());
    Console.WriteLine(ifunc.Invoke());
}

示例二:常量+變量(2.2示例)

namespace e1
{
    using System;
    using System.Linq.Expressions;
    class Program
    {
        static void Main(string[] args)
        {
            // 以此表達式爲例,手動拼接,實現相同做用
            Expression<Func<int, int, int>> func = (x, y) => x * y + 2;

            // 聲明變量表達式
            ParameterExpression px = Expression.Parameter(typeof(int), "x");
            ParameterExpression py = Expression.Parameter(typeof(int), "y");

            // 聲明常量表達式
            ConstantExpression constant = Expression.Constant(2, typeof(int));

            // 聲明乘積表達式
            BinaryExpression multiply = Expression.Multiply(px, py);

            // 聲明相加表達式
            BinaryExpression add = Expression.Add(multiply, constant);

            // 聲明參數表達式
            ParameterExpression[] parameters = new ParameterExpression[] { px, py };

            // 生成表達式目錄樹
            Expression<Func<int, int, int>> exp = Expression.Lambda<Func<int, int, int>>(add, parameters);

            // 表達式目錄樹生成委託
            var ifunc = exp.Compile();

            Console.WriteLine("委託結果:" + func.Compile().Invoke(1, 2));
            Console.WriteLine("表達式樹:" + ifunc.Invoke(1, 2));
        }
    }
}

3.2 變量,常量,方法拼接

示例一:特殊類型示例

namespace e1
{
    using System;
    using System.Linq.Expressions;
    using System.Reflection;

    class Program
    {
        static void Main(string[] args)
        {
            Test2();
        }

        static void Test2()
        {
            // lambda方式
            Expression<Func<User, bool>> func = (u) => u.uid.ToString().Equals("1");

            // 聲明變量表達式
            ParameterExpression x = Expression.Parameter(typeof(User), "x");

            // 獲取字段
            PropertyInfo property = typeof(User).GetProperty("uid");

            // 獲取方法
            MethodInfo toString = typeof(int).GetMethod("ToString", new Type[] { });
            MethodInfo equals = typeof(string).GetMethod("Equals", new Type[] { typeof(string) });

            // 設置常量表達式
            ConstantExpression constant = Expression.Constant("1");

            // 訪問字段表達式
            MemberExpression propertyExp = Expression.Property(x, property);

            // 調用方法表達式
            var tostringExp = Expression.Call(propertyExp, toString, new Expression[0]);
            var equalsExp = Expression.Call(tostringExp, equals, new Expression[] { constant });

            // 生成表達式樹
            Expression<Func<User, bool>> expression = 
                Expression.Lambda<Func<User, bool>>(equalsExp, new ParameterExpression[] { x });

            User user = new User { uid = 5 };

            Console.WriteLine(func.Compile().Invoke(user));
            Console.WriteLine(expression.Compile().Invoke(user));
        }
    }

    public class User
    {
        public int uid { get; set; }
    }
}

4. 解析Expression

使用 ExpressionVisitor 解析表達式目錄樹,ExpressionVisitor 表示表達式樹的訪問者和重寫者

解析流程

  • 經過ExpressionVisitor 這個訪問者類
  • 調用 Visit 入口(開始)方法解析表達式(自動根據表達式類型執行相應類型的解析方法)
  • Lambda 會區分參數和方法體,調度(自動)到更加專業的方法中解析(須要在次調用入口 Visit方法)
  • 根據表達式的類型,調度(自動)到更加專業的方法中解析(須要在次調用入口 Visit方法)
  • 根據舊的模式(表達式)產生一個新的表達式(若是須要重寫的話)
  • 說明:解析順序是從右往左解析(若是是二元表達式)

4.1 簡單解析

示例一:常量

using System;
using System.Linq.Expressions;

namespace e2
{
    class Program
    {
        static void Main(string[] args)
        {
            Test1();
        }

        static void Test1()
        {
            Expression<Func<int>> expression = () => 1;

            CustomVisitor visitor = new CustomVisitor();
            var exp = visitor.Modify(expression);

        }
    }
    public class CustomVisitor : ExpressionVisitor
    {
        public Expression Modify(Expression expression)
        {
            return this.Visit(expression);
        }
        
        protected override Expression VisitConstant(ConstantExpression node)
        {
            Console.WriteLine("VisitConstant");
            return base.VisitConstant(node);
        }
    }
}

示例二:變量+常量,算術運算(二元運算類型【兩個數操做】)

using System;
using System.Linq.Expressions;

namespace e2
{
    class Program
    {
        static void Main(string[] args)
        {
            Test1();
        }

        static void Test1()
        {
            // 1.建立表達式樹
            Expression<Func<int, int>> expression = (y) => y + 2;
			
            // 2.建立訪問類實例(使用繼承是爲了演示過程)
            CustomVisitor visitor = new CustomVisitor();
            // 3.調用入口方法,調用入口方法後就會自動進行默認解析(若是沒有定義解析過程的話)
            var exp = visitor.Modify(expression);
        }
    }
    
    // 繼承訪問類,演示過程
    public class CustomVisitor : ExpressionVisitor
    {
        // 調用入口方法,開始解析,自動調用表達式類型對應的解析方法
        public Expression Modify(Expression expression)
        {
            return this.Visit(expression);
        }
		
        // 4.調用二元表達式解析方法
        protected override Expression VisitBinary(BinaryExpression node)
        {
            Console.WriteLine("VisitBinary");
            // 4.1 判斷表達式操做類型
            if (node.NodeType == ExpressionType.Add)
            {
                Expression left = this.Visit(node.Left);
                Expression right = this.Visit(node.Right);

                return Expression.Subtract(left, right);
            }
            return base.VisitBinary(node);
        }
		// 4.調用常量表達式解析方法
        protected override Expression VisitConstant(ConstantExpression node)
        {
            Console.WriteLine("VisitConstant");
            return base.VisitConstant(node);
        }
    }
}

4.2 特殊解析

示例一:屬性+常量(二元運算)

using System;
using System.Linq.Expressions;

namespace e2
{
    class Program
    {
        static void Main(string[] args)
        {
            Test1();
        }

        static void Test1()
        {
            Expression<Func<User, bool>> expression = (u) => u.age > 1;

            CustomVisitor visitor = new CustomVisitor();
            var exp = visitor.Modify(expression);
        }
    }

    public class User
    {
        public int uid { get; set; }
        public int age { get; set; }
    }

    public class CustomVisitor : ExpressionVisitor
    {
        public Expression Modify(Expression expression)
        {
            return this.Visit(expression);
        }

        // 二元運算類型
        protected override Expression VisitBinary(BinaryExpression node)
        {
            Console.WriteLine("VisitBinary");
            if (node.NodeType == ExpressionType.Add)
            {
                Expression left = this.Visit(node.Left);
                Expression right = this.Visit(node.Right);

                return Expression.Subtract(left, right);
            }
            return base.VisitBinary(node);
        }

        // 屬性類型
        protected override Expression VisitMember(MemberExpression node)
        {
            Console.WriteLine("VisitMember");
            return base.VisitMember(node);
        }

        // 常量類型
        protected override Expression VisitConstant(ConstantExpression node)
        {
            Console.WriteLine("VisitConstant");
            return base.VisitConstant(node);
        }
    }
}

示例二:方法(若是要自定義解析處理的話須要預先知道方法名纔可)

using System;
using System.Linq.Expressions;

namespace e2
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression<Func<User, bool>> expression = (u) => u.name.Contains("1");
            CustomVisitor visitor = new CustomVisitor();
            visitor.Visit(expression);
        }
    }

    public class User
    {
        public int uid { get; set; }
        public int age { get; set; }
        public string name { get; set; }
    }

    public class CustomVisitor : ExpressionVisitor
    {
        public Expression Modify(Expression expression)
        {
            return this.Visit(expression);
        }

        // 方法表達式
        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            Console.WriteLine("VisitMethodCall:"+ node.Method.Name);
            return node;
        }
    }
}

5. 應用:解析Expression示例

5.1 示例一:簡單示例,生成SQL

運算符擴展方法

using System;
using System.Linq.Expressions;

namespace e2
{
    internal static class SqlOperator
    {
        internal static string ToSqlOperator(this ExpressionType type)
        {
            switch (type)
            {
                case (ExpressionType.AndAlso):
                case (ExpressionType.And):
                    return "AND";
                case (ExpressionType.OrElse):
                case (ExpressionType.Or):
                    return "OR";
                case (ExpressionType.Not):
                    return "NOT";
                case (ExpressionType.NotEqual):
                    return "<>";
                case ExpressionType.GreaterThan:
                    return ">";
                case ExpressionType.GreaterThanOrEqual:
                    return ">=";
                case ExpressionType.LessThan:
                    return "<";
                case ExpressionType.LessThanOrEqual:
                    return "<=";
                case (ExpressionType.Equal):
                    return "=";
                default:
                    throw new Exception("不支持該方法");
            }
        }
    }
}

表達式樹解析類

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace e2
{
    public class ConditionBuilderVisitor : ExpressionVisitor
    {
        private Stack<string> _StringStack = new Stack<string>();

        public string Condition()
        {
            string condition = string.Concat(this._StringStack.ToArray());
            this._StringStack.Clear();
            return condition;
        }

        // 解析 二元表達式 類型
        protected override Expression VisitBinary(BinaryExpression node)
        {
            if (node == null) throw new ArgumentNullException("BinaryExpression");

            this._StringStack.Push(")");
            base.Visit(node.Right);//解析右邊
            this._StringStack.Push(" " + node.NodeType.ToSqlOperator() + " ");
            base.Visit(node.Left);//解析左邊
            this._StringStack.Push("(");

            return node;
        }
        
        // 解析 屬性表達式 類型
        protected override Expression VisitMember(MemberExpression node)
        {
            if (node == null) throw new ArgumentNullException("MemberExpression");
            this._StringStack.Push(" [" + node.Member.Name + "] ");
            return node;
        }

        // 解析 常量表達式 類型
        protected override Expression VisitConstant(ConstantExpression node)
        {
            if (node == null) throw new ArgumentNullException("ConstantExpression");
            this._StringStack.Push(node.Value.ToString());
            return node;
        }
        
        // 解析 方法表達式 類型
        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            if (m == null) throw new ArgumentNullException("MethodCallExpression");

            string format;
            switch (m.Method.Name)
            {
                case "StartsWith":
                    format = "({0} LIKE '{1}%')";
                    break;

                case "Contains":
                    format = "({0} LIKE '%{1}%')";
                    break;

                case "EndsWith":
                    format = "({0} LIKE '%{1}')";
                    break;
                default:
                    throw new NotSupportedException(m.NodeType + " is not supported!");
            }
            this.Visit(m.Object);
            this.Visit(m.Arguments[0]);
            string right = this._StringStack.Pop();
            string left = this._StringStack.Pop();
            this._StringStack.Push(String.Format(format, left, right));

            return m;
        }
    }
}

調用執行

using System;
using System.Linq.Expressions;

namespace e2
{
    class Program
    {
        static void Main(string[] args)
        {
            Test1();
        }

        static void Test1()
        {
            Expression<Func<User, bool>> expression = (u) => u.age > 1;
            expression = (u) => u.age > 1 && u.uid < 2;
            expression = (u) => u.age > 1 && (u.uid < 2 || u.age > 2);
            expression = (u) => u.age > 1 && u.name.Contains("李");
            ConditionBuilderVisitor visitor = new ConditionBuilderVisitor();
            var exp = visitor.Visit(expression);
            Console.WriteLine(visitor.Condition());
        }
    }

    public class User
    {
        public int uid { get; set; }
        public int age { get; set; }
    }
}

5.2 示例二:表達式樹連接,生成SQL

連接表達式擴展方法

using System;
using System.Linq.Expressions;

namespace e2
{
    // 創建新表達式
    internal class NewExpressionVisitor : ExpressionVisitor
    {
        // 遍歷表達式類型,當遇到參數類型表達式時,替換爲咱們本身定義的參數
        public ParameterExpression _NewParameter { get; private set; }
        public NewExpressionVisitor(ParameterExpression param)
        {
            this._NewParameter = param;
        }
        public Expression Replace(Expression exp)
        {
            return this.Visit(exp);
        }
        // 利用ExpressionVisitor統一參數
        protected override Expression VisitParameter(ParameterExpression node)
        {
            return this._NewParameter;
        }
    }

    /// <summary>
    /// 合併表達式 And Or  Not擴展
    /// </summary>
    public static class ExpressionExtend
    {
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
        {
            ParameterExpression newParameter = Expression.Parameter(typeof(T), "c");
            NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter);

            var left = visitor.Replace(expr1.Body);// 從新生成了一個表達式目錄樹
            var right = visitor.Replace(expr2.Body);
            var body = Expression.And(left, right);
            return Expression.Lambda<Func<T, bool>>(body, newParameter);

        }
        
        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
        {
            // 建立參數表達式
            ParameterExpression newParameter = Expression.Parameter(typeof(T), "c");
            // 生成一個新的表達式
            NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter);

            var left = visitor.Replace(expr1.Body);
            var right = visitor.Replace(expr2.Body);
            var body = Expression.Or(left, right);
            return Expression.Lambda<Func<T, bool>>(body, newParameter);
        }

        public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expr)
        {
            var candidateExpr = expr.Parameters[0];
            var body = Expression.Not(expr.Body);

            return Expression.Lambda<Func<T, bool>>(body, candidateExpr);
        }
    }
}

解析表達式類

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace e2
{
    public class ConditionBuilderVisitor : ExpressionVisitor
    {
        private Stack<string> _StringStack = new Stack<string>();

        public string Condition()
        {
            string condition = string.Concat(this._StringStack.ToArray());
            this._StringStack.Clear();
            return condition;
        }

        // 解析 二元表達式 類型
        protected override Expression VisitBinary(BinaryExpression node)
        {
            if (node == null) throw new ArgumentNullException("BinaryExpression");

            this._StringStack.Push(")");
            base.Visit(node.Right);//解析右邊
            this._StringStack.Push(" " + node.NodeType.ToSqlOperator() + " ");
            base.Visit(node.Left);//解析左邊
            this._StringStack.Push("(");

            return node;
        }
        
        // 解析 屬性表達式 類型
        protected override Expression VisitMember(MemberExpression node)
        {
            if (node == null) throw new ArgumentNullException("MemberExpression");
            this._StringStack.Push(" [" + node.Member.Name + "] ");
            return node;
        }

        // 解析 常量表達式 類型
        protected override Expression VisitConstant(ConstantExpression node)
        {
            if (node == null) throw new ArgumentNullException("ConstantExpression");
            this._StringStack.Push(node.Value.ToString());
            return node;
        }

        // 解析 方法表達式 類型
        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            if (m == null) throw new ArgumentNullException("MethodCallExpression");

            string format;
            switch (m.Method.Name)
            {
                case "StartsWith":
                    format = "({0} LIKE '{1}%')";
                    break;

                case "Contains":
                    format = "({0} LIKE '%{1}%')";
                    break;

                case "EndsWith":
                    format = "({0} LIKE '%{1}')";
                    break;
                default:
                    throw new NotSupportedException(m.NodeType + " is not supported!");
            }
            this.Visit(m.Object);
            this.Visit(m.Arguments[0]);
            string right = this._StringStack.Pop();
            string left = this._StringStack.Pop();
            this._StringStack.Push(String.Format(format, left, right));

            return m;
        }
    }
}

調用執行

using System;
using System.Linq.Expressions;

namespace e2
{
    class Program
    {
        static void Main(string[] args)
        {
            Test1();
        }

        static void Test1()
        {
            Expression<Func<User, bool>> expression = (u) => u.age > 1;
            expression = expression.And(x=>x.uid>2);
            expression = expression.Or(x=>x.uid>2);
            expression = expression.Not();
            ConditionBuilderVisitor visitor = new ConditionBuilderVisitor();
            var exp = visitor.Visit(expression);
            Console.WriteLine(visitor.Condition());
        }
    }

    public class User
    {
        public int uid { get; set; }
        public int age { get; set; }
        public string name { get; set; }
    }
}

5.3 示例三:實體映射

場景;DTO 類轉換爲 Model

方案一:手動,硬編碼,不易出錯,效率高,但太繁瑣

[HttpPost]
public IActionResult Sava(UserDTD dtd)
{
    User user = new User
    {
      	uid = dtd.id,
        uname = dtd.name
    };
    _userBll.Sava(user);
}

方案二:使用反射,損耗高,兩個類型的屬性類型和名稱需保證一致

/// <summary>
/// 反射映射
/// </summary>
public class ReflectionMapper
{
    /// <summary>
    /// 實體轉換
    /// </summary>
    /// <typeparam name="T">傳入類型</typeparam>
    /// <typeparam name="TResult">返回值類型</typeparam>
    /// <param name="tIn">傳入參數</param>
    /// <returns>轉換好的實體</returns>
    public static TResult Trans<T, TResult>(T tIn)
    {
        TResult tOut = Activator.CreateInstance<TResult>();
        foreach (var itemOut in tOut.GetType().GetProperties())
        {
            var propIn = tIn.GetType().GetProperty(itemOut.Name);
            itemOut.SetValue(tOut, propIn.GetValue(tIn));
        }

        foreach (var itemOut in tOut.GetType().GetFields())
        {
            var fieldIn = tIn.GetType().GetField(itemOut.Name);
            itemOut.SetValue(tOut, fieldIn.GetValue(tIn));
        }

        return tOut;
    }
}

方案三:序列化反序列化,損耗高,兩個類型的屬性類型和名稱需保證一致

/// <summary>
/// 使用第三方序列化反序列化工具
/// </summary>
public class SerializeMapper
{
    /// <summary>
    /// 實體轉換
    /// </summary>
    public static TResult Trans<T, TResult>(T tIn)
    {
        return JsonConvert.DeserializeObject<TResult>(JsonConvert.SerializeObject(tIn));
    }
}

方案四:表達式目錄樹 + 字典緩存

/// <summary>
/// 生成表達式目錄樹 字典緩存
/// </summary>
public class ExpressionMapper
{
    /// <summary>
    /// 字典緩存--hash分佈
    /// </summary>
    private static Dictionary<string, object> _dic = new Dictionary<string, object>();

    /// <summary>
    /// 實體轉換
    /// </summary>
    public static TResult Trans<T, TResult>(T tIn)
    {
        string key = string.Format("funckey_{0}_{1}", typeof(T).FullName, typeof(TResult).FullName);
        if (!_dic.ContainsKey(key))
        {
            ParameterExpression parameterExpression = Expression.Parameter(typeof(T), "p");
            List<MemberBinding> memberBindingList = new List<MemberBinding>();
            foreach (var item in typeof(TResult).GetProperties())
            {
                MemberExpression property = Expression.Property(parameterExpression, typeof(T).GetProperty(item.Name));
                MemberBinding memberBinding = Expression.Bind(item, property);
                memberBindingList.Add(memberBinding);
            }
            foreach (var item in typeof(TResult).GetFields())
            {
                MemberExpression property = Expression.Field(parameterExpression, typeof(T).GetField(item.Name));
                MemberBinding memberBinding = Expression.Bind(item, property);
                memberBindingList.Add(memberBinding);
            }
            MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TResult)), memberBindingList.ToArray());
            Expression<Func<T, TResult>> lambda = Expression.Lambda<Func<T, TResult>>(memberInitExpression, new ParameterExpression[]
            {
                parameterExpression
            });
            Func<T, TResult> func = lambda.Compile(); //調用Compile方法將表達式轉換成委託
            _dic[key] = func; //拼裝是一次性的
        }

        return ((Func<T, TResult>)_dic[key]).Invoke(tIn);
    }
}

方案五:表達式目錄樹 + 泛型緩存(泛型緩存特色:爲不一樣類型的組合去緩存一個結果)

/// <summary>
/// 生成表達式目錄樹  泛型緩存
/// </summary>
/// <typeparam name="T">傳入參數類型</typeparam>
/// <typeparam name="TResult">返回值類型</typeparam>
public class ExpressionGenericMapper<T, TResult>
{
    /// <summary>
    /// 泛型緩存
    /// </summary>
    private static Func<T, TResult> _func = null;

    /// <summary>
    /// 靜態構造函數(只會被調用一次)
    /// </summary>
    static ExpressionGenericMapper()
    {
        ParameterExpression parameterExpression = Expression.Parameter(typeof(T), "p");
        List<MemberBinding> memberBindingList = new List<MemberBinding>();
        foreach (var item in typeof(TResult).GetProperties())
        {
            MemberExpression property = Expression.Property(parameterExpression, typeof(T).GetProperty(item.Name));
            MemberBinding memberBinding = Expression.Bind(item, property);
            memberBindingList.Add(memberBinding);
        }
        foreach (var item in typeof(TResult).GetFields())
        {
            MemberExpression property = Expression.Field(parameterExpression, typeof(T).GetField(item.Name));
            MemberBinding memberBinding = Expression.Bind(item, property);
            memberBindingList.Add(memberBinding);
        }
        MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TResult)), memberBindingList.ToArray());
        Expression<Func<T, TResult>> lambda = Expression.Lambda<Func<T, TResult>>(memberInitExpression, new ParameterExpression[]
        {
            parameterExpression
        });
        _func = lambda.Compile();//拼裝是一次性的
    }

    /// <summary>
    /// 實體轉換
    /// </summary>
    public static TResult Trans(T t)
    {
        return _func(t);
    }
}

6. 擴展補充

6.1 Lambda表達式本質

經過反編譯工具得知,Lambda表達式,其實就是一個方法,在中間語言中,爲其分配了一個方法名稱(<>

6.2 新語法:擴展方法

注意事項

  • 實例方法優先於擴展方法(容許存在同名實例方法和擴展方法),注意優先級
  • 能夠在空引用上調用擴展方法
  • 擴展方法必須放在一個非嵌套、非泛型的靜態類中,能夠被繼承
  • 至少有一個參數,第一個參數必須附加this關鍵字,不能有任何其餘修飾符(out/ref

編譯結果

public static class Extend
{
    public static int ToInt(this int? k)
    {
        return k ?? 0;
    }
}
.class public auto ansi abstract sealed beforefieldinit lq1.Extend
	extends [mscorlib]System.Object
{
	.custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = (
		01 00 00 00
	)
	// Methods
	.method public hidebysig static 
		int32 ToInt (
			valuetype [mscorlib]System.Nullable`1<int32> k
		) cil managed 
	{
		.custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = (
			01 00 00 00
		)
		// Method begins at RVA 0x216c
		// Code size 13 (0xd)
		.maxstack 1
		.locals init (
			[0] int32
		)

		IL_0000: nop
		IL_0001: ldarga.s k
		IL_0003: call instance !0 valuetype [mscorlib]System.Nullable`1<int32>::GetValueOrDefault()
		IL_0008: stloc.0
		IL_0009: br.s IL_000b

		IL_000b: ldloc.0
		IL_000c: ret
	} // end of method Extend::ToInt

} // end of class lq1.Extend

6.3 Linq To Object/Sql

linq to object

聲明的方法在 Enumerable 類中,針對於 Enumerable進行處理,數據來以內存數據

操做的表達式是一個委託

inq to sql

聲明的方法在 Queryable 類中,針對於 Queryable進行處理,數據來以內存數據或來自數據庫的的數據源

操做的表達式是一個表達式目錄樹,經過表達式目錄樹解析成SQL語句

6.4 yield 迭代器

using System;
using System.Collections.Generic;

namespace lq2
{
    class Program
    {
        static void Main(string[] args)
        {
            List<User> list = new List<User>
            {
                new User(){uid=1,uname="a1",age=18,gender=0 },
                new User(){uid=2,uname="a2",age=28,gender=1 },
                new User(){uid=3,uname="a3",age=23,gender=1 },
                new User(){uid=4,uname="a4",age=18,gender=0 },
                new User(){uid=5,uname="a5",age=33,gender=1 }
            };

            var d1 = list.MyWhere(x => x.uid > 3);
            foreach (var item in d1)
            {
                Console.WriteLine(item.uid);
            }
        }
    }

    public class User
    {
        public int uid { get; set; }
        public int age { get; set; }
        public string uname { get; set; }
        public int gender { get; set; }
    }

    public static class Extend
    {
        public static IEnumerable<T> MyWhere<T>(this IEnumerable<T> rouse, Func<T, bool> func)
        {
            
            foreach (var item in rouse)
            {
                if (func(item))
                {
                    // yield 迭代器一般與 Enumerable共同使用,實現按需獲取(延遲加載)
                    yield return item;  
                }
            }
        }
    }
}

6.5 表達式目錄樹與委託

Expression通常都是都是配合委託一塊兒來使用的,好比和委託Action,Func

Expression<Func<T>>是能夠轉成Func的(經過compile()方法轉換),反之則不行

6.6 ORM與表達式樹目錄的關係

日常項目中常常用到的EF操做時的擴展方法(Where之類的)其實傳的就是表達式目錄樹

相關文章
相關標籤/搜索