C#高級編程六十六天----表達式樹總結【轉】
https://blog.csdn.net/shanyongxu/article/details/47257139
表達式樹總結
基礎
表達式樹提供了一個將可執行代碼轉換成數據的方法.如果你要在執行代碼之前修改或轉換此代碼,那麽它是很有用的.有其是當你要將C#代碼----如LINQ查詢表達式轉換成其他代碼在另一個程序----如SQL數據庫裏操作它.
表達式樹的語法:
考慮下面簡單的Lambda表達式:
Func<int,int,int>function=(a,b)=>a+b;
這個語法包含三個部分:
1.一個聲明 : Func<int,int,int>function
2.一個等號 : =
3.一個Lambda表達式 : (a,b)=>a+b
變量function指向兩個數字相加的原聲可執行代碼.上面散的Lambda表達式表示一個簡短的如下的手寫方法:
public int function(int a,int b)
{
return a+b;
}
上面的方法或lambda表達式都可以這樣調用:
int c=function(3,4);
當上面的方法調用後,變量c將被設成3+4,變成7
上面聲明中第一步委托類型Func是在System命名空間中為我們定義好的:
public delegate TResult FUnc<T1,T2,TResult>(T1 arg1,T2 arg2);
這個代碼看上去很復雜,但他在這裏只是用來幫助我們定義變量function,變量function賦值為非常簡單的兩個數字相加的Lambda表達式.及時你不懂委托和泛型,仍然應該清楚這是一個聲明可執行代碼變量引用的方法.在這個例子裏它指向一個非常簡單的可執行代碼.
將代碼轉換到數據中
我們已經看到怎麽聲明一個指向原聲可執行代碼的變量.表達式樹不是可執行代碼,他是一種數據結構,那麽我們怎從表達式的原聲帶嗎轉換成表達式樹?怎麽從代碼轉換成數據?
LINQ提供一個簡單的用法將代碼轉換到名叫表達式樹的數據結構.首先添加命名空間:Linq.Expression
現在我們創建一個表達式樹
Expression<Func<int,int,int>>expression=(a,b)=>a+b;
和上例一樣的Lambda表達式用來轉換到類型為Expression<T>的表達式樹.標示expression不是可執行代碼了他還是一個名叫表達式樹的數據結構.
Visual Studio 2008的samples包含一個叫ExpressionTreeVisualizer的程序。它可以用來呈現表達式樹。圖1你可以看到一個展示上面簡單表達式語句的對話框截圖。註意,對話框上面部分顯示的是lambda表達式,下面是用TreeView控件顯示的其組成部分
編寫代碼來探索表達式樹
我們的例子是一個Expression<TDelegate>.Exoression<TDelegate>類有四個屬性:
Body:得到表達式的主體
Parameters:得到lambda 表達式的參數
NodeType:獲取樹的節點的ExpressionType.共45種不同的值,包含所有表達式節點各種可能的類型,例如返貨常量,例如返回參數,例如取兩個值的小值(<),例如取兩個值的大值(>),例如將值相加(+),等等.
Type:獲取表達式的一個靜態類型.在這個例子裏,表達式的類型是Func<int,int,int>.
如果我們折疊圖1的節點,Expression<TDelegate>的四個屬性便顯示的很清楚:
圖2:將樹節點折疊起來,你可以很容易的看到Expression<TDelegate>類的四個主要屬性.
你可以使用這四個屬性開始探索表達式樹.例如,你可以通過這樣找到參數的名稱:
Console.WriteLine(“參數1 : {0} , 參數2 : {1}”,expression.Parameters[0],expression.Parameters[1]);
這句代碼取出值a和b:
參數1: a ,參數2: b
這個很容易在圖1的ParameterExpression節點找到
讓我們在接下來的代碼探索表達式的Body,在這個例子裏是(a+b):
BinaryExpression body = (BinaryExpression)expression.Body;
ParameterExpression left = (ParameterExpression)body.Left;
ParameterExpression right = (ParameterExpression)body.Right;
Console.WriteLine(expression.Body);
Console.WriteLine(" 表達式左邊部分: " + "{0}{4} 節點類型: {1}{4} 表達式右邊部分: {2}{4} 類型: {3}{4}", left.Name, body.NodeType, right.Name, body.Type, Environment.NewLine);
這段代碼產生如下輸入:
(a + b)
表達式左邊部分: a
節點類型: Add
表達式右邊部分: b
類型: System.Int32
同樣,你會發現很容易在圖1的Body節點中找到這些信息。
通過探索表達式樹,我們可以分析表達式的各個部分發現它的組成。你可以看見,我們的表達式的所有元素都展示為像節點這樣的數據結構。表達式樹是代碼轉換成的數據。
編譯一個表達式:將數據轉換會代碼
如果我們可以將代碼轉換到數據,那麽我們也應該能將數據轉換會代碼.這裏是讓編譯器將表達式樹轉換到可執行代碼的簡單代碼.
int result=expression.Compile()(3,5);
Console.WriteLine(result);
這段代碼會輸出值8,跟本文最初生命的Lambda函數的執行結果一樣.
IQueryable<T>和表達式
現在至少你有一個抽象的概念理解表達式樹,現在是時候回來理解其在LINQ中的關鍵作用了,有其是在LINQ to SQL中.花點時間考慮這個標準的LINQ to SQL查詢表達式:
var query = from c in db.Customers
where c.City == "Nantes"
select new { c.City, c.CompanyName };
你可能知道,這裏LINQ表達式返回的變量query是IQuerable類型,這裏是IQueryable類型的定義:
public interface IQueryable:IEnumerable
{
Type ElementType{get;}
Expression Expression{get;}
IQueryProvider Provider{get;}
}
可以看到,IQueryable包含一個類型為Expression的屬性,Expression是Expression<T>的基類.IQuerabl餓的實例被設計成擁有一個相關的表達式樹.它是一個等同於查詢表達式中的可執行代碼的數據結構.
為什麽要將LINQ to SQL查詢表達式轉換成表達式樹呢?
表達式樹是一個用來表示可執行代碼的數據結構.但到目前為止我們仍然存在一個核心問題,那就是我們為什麽要這麽做?
一個LINQ to SQL查詢不是在你的C#程序裏執行的.相反,他被轉換成SQL,通過網絡發送,最後在數據庫服務器上執行.換句話說,下面的代碼實際上從來不會在你的程序裏執行:
var query=from c in db.Customers
where c.City==”BeiJing”
select new {c.City,c.CompantName};
他首先被轉換成下面的SQL語句然後在服務器上執行:
SELECT [t0].[City], [t0].[CompanyName]
FROM [dbo].[Customers] AS [t0]
WHERE [t0].[City] = @p0
從查詢表達式的代碼轉換成SQL查詢語句----他可以通過字符串形式被發送到其他程序.在這裏,這個程序恰好是SQL Server數據庫.像這樣將數據結構轉換到SQL顯然比直接從原聲IL或可執行代碼轉換到SQL要容易的多.這有些誇大問題的難度,只要事項轉換0和1的序列到SQL.
現在是時候將你的查詢表達式轉換成SQL,描述查詢的表達式樹是分解並解析了的,就像分解簡單的Lambda表達式樹一樣.當然,解析LINQ to SQL表達式樹的算法很復雜,但規則是一樣的,一旦了解了表達式樹的各部分,那麽LINQ開始斟酌以最好的方式生成返回被請求的數據的SQL語句.
表達式樹被創建是為了制造一個像查詢表達式轉換成字符串以傳遞給其他程序並在那裏執行這樣的轉換任務,就是這麽簡單.沒有什麽特別的.只是簡單的:把代碼,轉換成數據,然後分析數據發現其組成部分,最後轉換成可以傳遞到其他程序的字符串.
於查詢來自編譯器封裝的抽象的數據結構,編譯器可以獲取任何它想要的信息。它不要求執行查詢要在特定的順序,或用特定的方式。相反,它可以分析表達式樹,尋找你要做的是什麽,然後再決定怎麽去做。至少在理論上,我們可以自由的考慮各種因素,比如網絡狀況,數據庫負載,結果集是否有效,等等。在實際中LINQ to SQL不考慮所有這些因素,但它理論上可以自由的做幾乎所有想做的事。此外,人們可以通過表達式樹將自己編寫的代碼,分析並轉換成跟LINQ to SQL提供的完全不同的東西。
IQueryable<T>和IEnumerable<T>
正如你知道的,LINQ to Objects的查詢表達式返回IEnumerable<T>而不是IQueryable<T>.為什麽LINQ to Objects使用IEnumerable<T>而LINQ to SQL使用IQueryable<T>?
這裏是IEnumerable<T>的定義:
public interface IEnumerable<T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
正如你看到的,IEnumerable<T>並不包含類型為Expression的屬性。這指出LINQ to Objects和LINQ to SQL的根本區別。後者大量使用了表達式樹,但LINQ to Objects很少使用。
為什麽表達式樹不是LINQ to Objects的標準部分?雖然答案不一定會馬上出現,但這是很有意義的一旦你發現這個問題。
考慮這個簡單LINQ to Objects查詢表達式:
List<int> list = new List<int>() { 1, 2, 3 };
var query = from number in list
where number < 3
select number;
這個LINQ查詢返回在我們的list中比3小的數字;就是說,這裏返回數字1和2。顯然沒有必要將查詢轉換成字符串來順序傳遞給其他程序並獲取正確的結果。相反,可以直接轉換查詢表達式為可執行的.NET代碼。這裏並不需要將它轉換成字符串或對它執行任何其他復雜操作。
可是這有點理論化,在實際中某些特殊情況下其分隔線可能有些模糊,總體上講規則相當簡單:
如果代碼可以在程序裏執行那麽可以使用名為IEnumerable<T>的簡單類型完成任務
如果你需要將查詢表達式轉換成將傳遞到其他程序的字符串,那麽應該使用IQueryable<T>和表達式樹。
像LINQ to Amazon這樣的項目需要將查詢表達式轉換成web service調用執行外部程序,通常使用IQueryable<T>和表達式樹。LINQ to Amazon將它的查詢表達式轉換成數據,通過web service傳遞給另一個甚至不是C#寫的程序。將C#代碼轉換成到某些能傳遞到web service的東西時,表達式樹內在的抽象是非常有用的。要在程序內執行的代碼,仍然可以經常使用而拋開表達式樹。例如下面的查詢使用IEnumerable<T>,因為它調用到當前程序的.NET反射API:
var query = from method in typeof(System.Linq.Enumerable).GetMethods()
orderby method.Name
group method by method.Name into g
select new { Name = g.Key, Overloads = g.Count() };
概要:
本文覆蓋了表達式樹的一些基本情況。通過將代碼轉換成數據,這些數據結構揭示並描繪表達式的組成部分。從最小的概念上講,理解表達式樹是相當簡單的。它獲取可執行表達式並獲取其組成部分放入樹形數據結構。例如,我們檢測這個簡單的表達式:
(a,b) => a + b;
通過研究來源於這個表達式的樹,你能看到創建樹的基本規則,見圖1。
你同樣可以看到表達式樹在LINQ to SQL裏扮演非常重要的角色。尤其,他們是LINQ to SQL查詢表達式用來獲取邏輯的數據抽象。解析並分析此數據得到SQL語句,然後發送到服務器。
LINQ使查詢C#語言的一個普通類即有類型檢查也有智能感知。其代碼是類型檢查和智能感知的,必須使用正確的C#語法,它能直接轉換到可執行代碼,就像任何其他C#代碼一樣被轉換和執行。表達式樹使將可執行代碼轉換成能傳遞到服務器的SQL語句相對容易。
查詢返回IEnumerable<T>優於IQueryable<T>表示不使用表達式樹。作為一般性規則,可以這麽說:LINQ查詢在程序內執行時不需要表達式樹,當代碼在程序外執行時可以利用表達式樹。
擴展:Expression Tree Visualizer是一個集成子啊VS中的工具,用於在運行時以樹狀結構顯示出指定的Expression
使用方法:
下載安裝包:http://www.fishlee.net/service/download/589
解壓後根據你的VS版本選擇合適目錄下的 ExpressionTreeVisualizer.dll 文件,復制到你的VS安裝目錄下的Common7\Packages\Debugger\Visualizers 目錄裏
重啟VS
註意,只有在Debug的時候才能看到,
Expression表達式樹的實例
表達式樹表示樹狀數據結構的代碼,樹狀結構中的每個節點都是一個表達式,例如一個方法調用或類似x<y的二元運算
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
namespace 表達式樹
{
class Program
{
static void Main(string[] args)
{
//利用Lambda表達式創建表達式樹
Expression<Func<int, int, int, int>> expr = (x, y, z) => (x + y) / z;
//編譯表達式樹,該方法將表達式樹表示的代碼編譯成一個可執行委托
int res = expr.Compile()(1, 2, 3);
Console.WriteLine("利用Lambda表達式創建表達式樹 : " + res);
//使用LambdaExpression構建可執行的代碼
Func<int, int, int, int> fun = (x, y, z) => (x + y) / z;
Console.WriteLine("使用LambdaExpression構建可執行的代碼 : " + fun(1, 2, 3));
//動態構建表達式樹
ParameterExpression pe1 = Expression.Parameter(typeof(int), "x");
ParameterExpression pe2 = Expression.Parameter(typeof(int), "y");
ParameterExpression pe3 = Expression.Parameter(typeof(int), "z");
var body = Expression.Divide(Expression.Add(pe1, pe2), pe3);
var w = Expression.Lambda<Func<int, int, int, int>>(body, new ParameterExpression[]
{
pe1,pe2,pe3
});
Console.WriteLine("動態構建表達式樹 ; " + w.Compile()(1, 2, 3));
List<Entity> list = new List<Entity> { new
Entity { Id1 = 1 }, new Entity { Id1 = 2 }, new Entity { Id1 = 3 } };
//IQueryable<T>的擴展方法,WhereIn的實現
var d = list.AsQueryable().WhereIn(o => o.Id1, new int[] { 1, 2 });
d.ToList().ForEach(o =>
{
Console.WriteLine(o.Id1);
});
Console.ReadKey();
}
}
public class Entity
{
public Object Id;
public int Id1;
public string Name { set; get; }
}
public static class CC
{
public static IQueryable<T> WhereIn<T, TValue>(this
IQueryable<T> query, Expression<Func<T, TValue>> obj,
IEnumerable<TValue> values)
{
return query.Where(BuildContainsExpression(obj, values));
}
private static Expression<Func<TElement, bool>>
BuildContainsExpression<TElement,
TValue>(Expression<Func<TElement, TValue>> valueSelector,
IEnumerable<TValue> values)
{
if (null == valueSelector)
{
throw new ArgumentNullException("valueSelector");
}
if (null == values)
{
throw new ArgumentNullException("values");
}
var p = valueSelector.Parameters.Single();
if (!values.Any()) return e => false;
var equals = values.Select(value =>
(Expression)Expression.Equal(valueSelector.Body,
Expression.Constant(value, typeof(TValue))));
var body = equals.Aggregate(Expression.Or);
return Expression.Lambda<Func<TElement, bool>>(body, p);
}
}
}
C#高級編程六十六天----表達式樹總結【轉】