前言:Expression 是C# 高級編程,表達式的應用場景有 ORM框架:Entity Framework,Dapper等,規則引擎:動態業務規則評估, 依賴注入:高級DI容器實現,測試框架:模擬和斷言, 動態查詢構建:根據用戶輸入構建查詢 等等。 學習掌握Expression 對于構建動態邏輯的應用非常有幫助。
1.Expression 的由來
為了將Linq 查詢語句轉換成SQL語句,在其它外部服務器上執行。將代碼是給計算機執行的指令序列到代碼是可以通過分析,轉換和解釋的數據結構。
將代碼表示為數據結構,表達式架起了編譯時靜態世界到運行時動態世界的橋梁。
2. Expression的常用語法
2.1 Expression 特點
知道了Expression的目的,就是為了表達代碼的數據結構。那么表達式的很多特點跟代碼的表述形式很相近。
在學習一門語法之前,要先學習語句,變量,常量,賦值, 參數, 算術運算, 條件語句,循環語句,方法,類,對象,成員調用,異常 等等。
Expression 的類型有 NodeType 有84 種之多大多提供了表述以上代碼的形式。
這里舉個例子說明:
例1:想要定義一個變量,并且為這個變量賦值,打印輸出這個變量的值
ParameterExpression varExp = Expression.Variable(typeof(int), "i");ConstantExpression conExp = Expression.Constant(10, typeof(int));BinaryExpression assignExp = Expression.Assign(varExp, conExp);Expression lambda = Expression.Block(new[] { varExp }, assignExp, varExp);Expression<Func<int>> lambdaExp = Expression.Lambda<Func<int>>(lambda);Console.WriteLine(lambdaExp.Compile().Invoke());
既然知道了表達式的目的就是表述一段代碼結構,那這段代碼結構是可以被編譯器編譯成委托,然后被執行的。
Console.WriteLine(lambdaExp.Compile().Invoke());
- Expression 是所有表達式類型的基類。
- Expression 的類型能夠編寫的大多數代碼結構
- 通過表達式語句塊最終編譯動態生成一個委托
2.3 Expression核心組件
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection.Metadata;
using System.Text;
using System.Threading.Tasks;namespace Study01_Expression.Study1_Expression
{internal class _01_Expression{private static readonly Lazy<_01_Expression> _instance = new Lazy<_01_Expression>(() => new _01_Expression());public static _01_Expression Instance => _instance.Value;public void Test(){Study3();}/// <summary>/// 初識表達式的核心組件/// </summary>public void Study1(){// 1.表達式的基類: Expression// 2.參數表達式:ParameterExpression// 3.二元運算表達式:BinaryExpression// 4.常量表達式:ConsantExpression// 5 方法表達式:MethodCallExpression// 6.屬性或字段表達式: MemberExpression// 7.Lambda表達式:LambdaExpressionStringBuilder stringBuilder = new StringBuilder();stringBuilder.Append("\r\n1.表達式的基類: Expression");stringBuilder.Append("\r\n2.參數表達式:ParameterExpression");stringBuilder.Append("\r\n3.二元運算表達式:BinaryExpression");stringBuilder.Append("\r\n4.常量表達式:ConsantExpression");stringBuilder.Append("\r\n5.方法表達式:MethodCallExpression");stringBuilder.Append("\r\n6.屬性或字段表達式: MemberExpression");stringBuilder.Append("\r\n7.Lambda表達式:LambdaExpression");Console.WriteLine(stringBuilder.ToString());}/// <summary>/// 7類表達式的基本使用/// </summary>public void Study2(){// 1.基本元素的創建// 2.二元運算的基本使用// 3.成員表達式的基本使用// 4.控制流語句的使用// 5.Lambda 表達式的使用// 6.組合表達式的基本使用}/// <summary>/// 表達式基本原素的創建/// </summary>public void Study3(){//// 1.參數與常量//ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "X");//ConstantExpression constantExpression = Expression.Constant(10, typeof(int));//ConstantExpression constantExpression1 = Expression.Constant(10);//// 2.變量//ParameterExpression parameterExpression1 = Expression.Variable(typeof(int), "Name");//// 在Block中使用變量//BlockExpression blockExpression = Expression.Block(// new[] { parameterExpression1 },// Expression.Assign(parameterExpression1, Expression.Constant(5)),// parameterExpression1// );//Console.WriteLine(blockExpression.ToString());}/// <summary>/// 運算表達式方法/// </summary>public void Study4(){// 算術運算BinaryExpression binaryExpression = Expression.And(Expression.Constant(2), Expression.Constant(3));// 比較運算// =BinaryExpression binaryExpression1 = Expression.Equal(binaryExpression, Expression.Constant(4));// !=BinaryExpression binaryExpression2 = Expression.NotEqual(binaryExpression1, Expression.Constant(5));// >BinaryExpression binaryExpression3 = Expression.GreaterThan(binaryExpression1, binaryExpression2);// <BinaryExpression binaryExpression4 = Expression.LessThan(binaryExpression3, Expression.Constant(6));// 邏輯運算符// &BinaryExpression binaryExpression5 = Expression.Add(binaryExpression1, binaryExpression2);// |BinaryExpression binaryExpression6 = Expression.Or(binaryExpression5, binaryExpression4);// &&BinaryExpression binaryExpression7 = Expression.AndAlso(binaryExpression6, binaryExpression5);// || BinaryExpression binaryExpression8 = Expression.OrElse(binaryExpression6, binaryExpression7);// !UnaryExpression unaryExpression = Expression.Not(binaryExpression8);}/// <summary>/// 成員訪問與方法調用/// </summary>public void Study5(){// 1.屬性和字段的訪問ParameterExpression param = Expression.Parameter(typeof(int), "X");Expression.Field(param, "_internalField");Expression.Property(param, "Length");// 靜態成員的訪問Expression.Property(null, typeof(DateTime), "Now");ParameterExpression personParam = Expression.Parameter(typeof(Persion), "P");// 2.方法的調用// p.SayHello("World");Expression.Call(personParam,typeof(Persion).GetMethod("SayHello"),Expression.Constant("World"));// string.Concat("Hello","","World");// 靜態方法的調用Expression.Call(typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string), typeof(string) }),Expression.Constant("Hello"),Expression.Constant(""),Expression.Constant("World"));}/// <summary>/// 對象的創建與初始化/// </summary>public void Study6(){NewExpression newExpression = Expression.New(typeof(Persion).GetConstructor(new[] { typeof(string), typeof(int) }),Expression.Constant("Join"),Expression.Constant(25));Persion persion = Expression.Lambda<Func<Persion>>(newExpression).Compile().Invoke();persion.SayHello("你好");}/// <summary>/// Expression構建表達式樹/// </summary>public void Study7(){Expression<Func<int, int>> squareExpression = x => x * x;Console.WriteLine(squareExpression); // 輸出 x => (x * x)// 編譯并執行表達式樹Func<int, int> func = squareExpression.Compile();int v = func.Invoke(5);Console.WriteLine(v);}/// <summary>/// 動態調用任意方法/// </summary>public void Study8(){Persion persion = new Persion("嘻嘻", 6);ConstantExpression constantExpression = Expression.Constant(persion);System.Reflection.MethodInfo? methodInfo = persion.GetType().GetMethod("SayHello");ParameterExpression parameterExpression = Expression.Parameter(typeof(string), "str");MethodCallExpression methodCallExpression = Expression.Call(constantExpression, methodInfo, parameterExpression);Expression<Action<string>> expression = Expression.Lambda<Action<string>>(methodCallExpression, parameterExpression);expression.Compile().Invoke("你好");}}public class Persion{public string Name { get; set; }public int Age { get; set; }public Persion(string name, int age){Name = name;Age = age;}public void SayHello(string str){Console.WriteLine(string.Format("name{0},age{1},SayHello,{2}", Name, Age, str));}}
}
2.3 學習表達式的常見錯誤
變量的作用域在一個塊內才有效,變量可以定義在作用域的外面。但是變量必須放在塊內。
Lambda表達式的作用域對于 參數或者變量定義可以放在外面, 但是對于在body 內使用的參數或者變量,必須要在lambda 里面傳入參數或者變量表達式對象。否則就會出現在此作用域內未定義的異常報錯。
在lambda里面傳入變量表達式實例參數。
ParameterExpression jExp = Expression.Variable(typeof(int), "j");Expression<Func<int, int>> expression = Expression.Lambda<Func<int, int>>(Expression.Multiply(jExp, jExp), // 參數 j 在此作用域內可見jExp);Console.WriteLine(expression.Compile().Invoke(10));
因為這里委托傳入一個參數,但有時候不需要傳入參數,但是想要使用參數表達式,又必須在Lmbda 的主體作用域范圍里面有效,此時必須要借助表達式語句塊來實現。
Expression.Assign(jExp, Expression.Constant(5));BinaryExpression assign6 = Expression.Assign(jExp, Expression.Constant(6));BinaryExpression binaryExpression = Expression.Multiply(jExp, jExp);BinaryExpression binaryExpression1 = Expression.Assign(jExp, binaryExpression);BlockExpression blockExpression = Expression.Block(new[] { jExp }, assign6, binaryExpression, binaryExpression1, jExp);Expression<Func<int>> expression1 = Expression.Lambda<Func<int>>(blockExpression);Console.WriteLine(expression1.Compile().Invoke());
作用域這個對于初學者來說非常容易出錯,這個點需要注意哦。
3. Expression和 委托的區別
表達式動態編譯之后就是委托。
表達式描述可以根據代碼邏輯動態構建代碼結構,是動態的。
而委托是已經完成的,代表一個固定的代碼片段。
4. Expression的使用場景
- 依賴注入
在依賴注入的框架的實現,是通過表達式來完成的,其中包括創建者,對注入的不同類型創建一個對象,然后為對象屬性賦值,最后通過容器獲取到這個對象,這里的實現過程通過表達式來實現的。 - Linq多條件動態構建多條件查詢 Where 里面就是一個委托,通過構建表達式分析生成 委托
- ORM框架:Entity Framework,Dapper等 (這里暫時未遇到,等遇到再更新)
- 對象屬性映射