05.表示式目錄樹Expression

位永光發表於2021-08-13

參考文章

1. 基本瞭解

1.1 Lambda表示式

演變過程

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);
            }
        }
    }
}

概述說明

Lambda表示式是一個特殊的匿名函式,是一種高效的類似於函數語言程式設計的表示式,簡化開發中需要編寫的程式碼量

可以包含表示式和語句,並且可用於建立委託或表示式目錄樹型別,支援帶有可繫結到委託或表示式樹的輸入引數的內聯表示式

所有Lambda表示式都使用Lambda運算子=>,運算子的左邊是輸入引數(如果有),右邊是表示式或語句塊

Lambda表示式不能在委託鏈中進行刪(-=)操作,因為每個表示式的名稱都不一樣

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之類的)其實傳的就是表示式目錄樹

相關文章