首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

打造專屬 LINQ Provider(上):深入解析 Expression Tree 核心機制

     編輯:關於.NET

概述

自 .NET Framework 3.5 引入 LINQ 以來,其強大且優雅的查詢語法深受開發者喜愛。隨著 LINQ to NHibernate、LINQ to Google 等 Provider 層出不窮,「一切皆 LINQ」的趨勢愈發明顯。LINQ 本身具備優異的擴展性,讓我們能輕鬆打造專屬的 LINQ Provider。本文為系列首篇,將深入剖析表達式樹的核心知識,為後續實作奠基。

認識表達式樹

表達式樹本質上是一種抽象語法樹(AST)或資料結構,透過解析它可實現特定功能。最簡單的構造方式是使用 Lambda 表達式賦值給 Expression<TDelegate> 類型變數:

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

編譯器會生成表達式樹指令,將程式碼以資料結構形式呈現。上述 Lambda 表達式對應的樹狀結構如下:

表達式樹結構圖

每個節點代表一種表達式:ParameterExpression(參數)、ConstantExpression(常數)、BinaryExpression(二元運算)等。Visual Studio 內建的 Expression Tree Visualizer(可從 MSDN Code Gallery 的 LINQ Sample 取得)能直觀展示樹結構:

Visualizer 介面

Visualizer 結果

.NET 提供的表達式類型

所有表達式類別皆繼承自抽象基類 Expression,泛型 Expression<TDelegate> 則繼承 LambdaExpression。Expression 類別提供大量工廠方法建立各類表達式物件,例如 Add() 建立不檢查溢位的加法 BinaryExpression,Lambda() 建立 LambdaExpression。完整方法請參閱 MSDN。

除了透過 Lambda 自動生成,也可手動組合表達式樹:

static void Main(string[] args)
{
    ParameterExpression paraLeft = Expression.Parameter(typeof(int), "a");
    ParameterExpression paraRight = Expression.Parameter(typeof(int), "b");
    BinaryExpression binaryLeft = Expression.Multiply(paraLeft, paraRight);
    ConstantExpression conRight = Expression.Constant(2, typeof(int));
    BinaryExpression binaryBody = Expression.Add(binaryLeft, conRight);
    LambdaExpression lambda = Expression.Lambda<Func<int, int, int>>(binaryBody, paraLeft, paraRight);
    Console.WriteLine(lambda.ToString());
    Console.Read();
}

輸出結果與自動生成完全一致:

手動構造輸出

表達式樹關鍵屬性

  • Body:表達式主體部分
  • Parameters:參數集合
  • NodeType:節點類型(如 Lambda)
  • Type:靜態類型(如 Func<int,int,int>)

Expression Tree Visualizer 可檢視這些屬性:

樹屬性檢視

表達式樹與委托的差異

常見困惑:Func<int,int,int> lambda = (a,b) => a + b * 2; 與 Expression<Func<int,int,int>> expression = (a,b) => a + b * 2; 差異何在?

前者編譯為直接可執行的 IL 程式碼;後者編譯器會分析 Lambda 並生成對應的表達式樹結構(等同手動構造的 IL)。這差異是理解表達式樹「可被解析、轉換」特性的關鍵。

執行表達式樹

呼叫 Compile() 即可產生可執行委托:

Expression<Func<int, int, int>> lambda = ...;
Func<int, int, int> myLambda = lambda.Compile();
int result = myLambda(2, 3); // 輸出 8

執行結果

內部由 ExpressionCompiler 處理(非傳統編譯器編譯)。僅 LambdaExpression 或 Expression<TDelegate> 可直接執行;其他類型需先用 Lambda() 包裝。

訪問與修改表達式樹

表達式樹是不可變的,「修改」實為建立副本。.NET 提供內部 ExpressionVisitor,MSDN 公開其實作供繼承。例如將 (a,b) => a + b * 2 轉為 (a,b) => a - (b * 2):

public class OperationsVisitor : ExpressionVisitor
{
    public Expression Modify(Expression expression) => Visit(expression);
    protected override Expression VisitBinary(BinaryExpression b)
    {
        if (b.NodeType == ExpressionType.Add)
        {
            Expression left = Visit(b.Left);
            Expression right = Visit(b.Right);
            return Expression.Subtract(left, right);
        }
        return base.VisitBinary(b);
    }
}

使用方式:

var lambda = (a, b) => a + b * 2;
var visitor = new OperationsVisitor();
Expression modified = visitor.Modify(lambda);
Console.WriteLine(modified.ToString()); // 輸出 (a, b) => a - (b * 2)

修改後結果

為何需要表達式樹?

以 LINQ to SQL 為例:查詢運算式封裝為 IQueryable,其核心屬性 Expression(表達式樹)與 Provider(查詢提供者)決定翻譯方式。這架構賦予極高彈性,讓我們能針對任意資料源編寫 Provider,實現「統一程式模型、多元資料存取」。

LINQ to SQL 架構

IQueryable 結構

注意:LINQ to Objects 不轉換為表達式樹,直接在記憶體執行。

總結

本文詳細介紹表達式樹的構造、屬性、執行、訪問修改及其在 LINQ 架構中的核心地位,為打造自訂 LINQ Provider 鋪路。下篇將實戰開發 Provider。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved