程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> LINQ表達式樹基礎

LINQ表達式樹基礎

編輯:關於.NET

剛接觸LINQ的人往往覺得表達式樹很不容易理解。通過這篇文章我希望大家 看到它其實並不像想象中那麼難。您只要有普通的LINQ知識便可以輕松理解本文 。

表達式樹提供一個將可執行代碼轉換成數據的方法。如果你要在執行代碼之 前修改或轉換此代碼,那麼它是非常有價值的。尤其是當你要將C#代碼----如 LINQ查詢表達式轉換成其他代碼在另一個程序----如SQL數據庫裡操作它。

但是我在這裡顛倒順序,在文章最後你很容易發現為什麼將代碼轉換到數據 中去很有用。首先我需要提供一點背景知識。讓我們開始看看相關的創建表達式 樹的簡單語法。

表達式樹的語法

考慮下面簡單的Lambda表達式:

Func<int, int, int> function = (a,b) => a + b;

這個語句包含三個部分:

一個聲明: Func<int, int, int> function

一個等號: =

一個lambda表達式: (a,b) => a + b;

變量function指向兩個數字相加的原生可執行代碼。上面三步的lambda表達 式表示一個簡短的如下的手寫方法:

public int function(int a, int b)
{
   return a + b;
}

上面的方法或lambda表達式都可以這樣調用:

int c = function(3, 5);

當方法調用後,變量c將被設成3+5,即8。

上面聲明中第一步委托類型Func是在System命名空間中為我們定義好的:

public delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2);

這個代碼看上去很復雜,但它在這裡只是用來幫我們定義變量function,變 量function賦值為非常簡單的兩個數字相加的lambda表達式。即使你不懂委托和 泛型,你仍然應該清楚這是一個聲明可執行代碼變量引用的方法。在這個例子裡 它指向一個非常簡單的可執行代碼。

將代碼轉換到數據中

在上一節,你看到怎麼聲明一個指向原生可執行代碼的變量。表達式樹不是 可執行代碼,它是一種數據結構。那麼我們怎麼從表達式的原生代碼轉換成表達 式樹?怎麼從代碼轉換成數據?

LINQ提供一個簡單語法用來將代碼轉換到名叫表達式樹的數據結構。首先添 加using語句引入Linq.Expressions命名空間:

using System.Linq.Expressions;

現在我們可以創建一個表達式樹:

Expression<Func<int, int, int>> expression = (a,b) => a + b;

跟上個例子一樣的lambda表達式用來轉換到類型為Expression<T>的表 達式樹。標識expression不是可執行代碼;它是一個名叫表達式樹的數據結構。

Visual Studio 2008的samples包含一個叫ExpressionTreeVisualizer的程序 。它可以用來呈現表達式樹。圖1你可以看到一個展示上面簡單表達式語句的對 話框截圖。注意,對話框上面部分顯示的是lambda表達式,下面是用TreeView控 件顯示的其組成部分。

圖1:VS2008 C# Samples中的ExpressionTreeVisualizer創建一個表達式樹 的象征性的輸出

編寫代碼來探索表達式樹

我們的例子是一個Expression<TDelegate>。 Expression<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是IQueryable類型。這裡是 IQueryable類型的定義:

public interface IQueryable : IEnumerable 
{
   Type ElementType { get; }
   Expression Expression { get; }
   IQueryProvider Provider { get; }
}

你可以看見,IQueryable包含一個類型為Expression的屬性,Expression是 Expression<T>的基類。IQueryable的實例被設計成擁有一個相關的表達 式樹。它是一個等同於查詢表達式中的可執行代碼的數據結構。

花點時間考慮圖3。你可能需要點擊它使圖片原尺寸顯示。這是本節開始的查 詢表達式的表達式樹的可視化顯示。此圖使用ExpressionTreeVisualizer創建, 就像我使用它在圖1創建基礎的lambda表達式樹一樣。

圖3:此復雜的表達式樹由上面的樣例LINQ to SQL查詢表達式生成。

為什麼要將LINQ to SQL查詢表達式轉換成表達式樹呢?

你已經學習了表達式樹是一個用來表示可執行代碼的數據結構。但到目前為 止我們還沒有回答一個核心問題,那就是為什麼我們要做這樣的轉換。這個問題 是我們在本文開始時提出來的,現在是時候回答了。

一個LINQ to SQL查詢不是在你的C#程序裡執行的。相反,它被轉換成SQL, 通過網絡發送,最後在數據庫服務器上執行。換句話說,下面的代碼實際上從來 不會在你的程序裡執行:

var query = from c in db.Customers
             where c.City == "Nantes"
             select new { c.City, c.CompanyName  };

它首先被轉換成下面的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查詢在程序內執行時不需要表達式樹, 當代碼在程序外執行時可以利用表達式樹。

原文來自Charlie Calvert的Expression Tree Basics

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved