C#3.0新增功能10 表達式樹 04 執行表達式

表達式樹 是表示一些代碼的數據結構。 它不是已編譯且可執行的代碼。 若是想要執行由表達式樹表示的 .NET 代碼,則必須將其轉換爲可執行的 IL 指令。html

Lambda 表達式到函數

能夠將任何 LambdaExpression 或派生自 LambdaExpression 的任何類型轉換爲可執行的 IL。 其餘表達式類型不能直接轉換爲代碼。 此限制在實踐中影響不大。 Lambda 表達式是你可經過轉換爲可執行的中間語言 (IL) 來執行的惟一表達式類型。 (思考直接執行 ConstantExpression 意味着什麼。 這是否意味着任何用處?)LambdaExpression 或派生自 LambdaExpression 的類型的任何表達式樹都可轉換爲 IL。 表達式類型 Expression<TDelegate> 是 .NET Core 庫中的惟一具體示例。 它用於表示映射到任何委託類型的表達式。 因爲此類型映射到一個委託類型,所以 .NET 能夠檢查表達式,併爲匹配 lambda 表達式簽名的適當委託生成 IL。算法

在大多數狀況下,這將在表達式和其對應的委託之間建立簡單映射。 例如,由 Expression<Func<int>> 表示的表達式樹將被轉換爲 Func<int> 類型的委託。 對於具備任何返回類型和參數列表的 Lambda 表達式,存在這樣的委託類型:該類型是由該 Lambda 表達式表示的可執行代碼的目標類型。express

LambdaExpression 類型包含用於將表達式樹轉換爲可執行代碼的 Compile 和 CompileToMethod成員。 Compile 方法建立委託。 CompileToMethod 方法經過表示表達式樹的已編譯輸出的 IL 更新 MethodBuilder 對象。 請注意,CompileToMethod 僅在完整的桌面框架中可用,不能用於 .NET Core。緩存

還能夠選擇性地提供 DebugInfoGenerator,它將接收生成的委託對象的符號調試信息。 這讓你能夠將表達式樹轉換爲委託對象,並擁有生成的委託的完整調試信息。數據結構

使用下面的代碼將表達式轉換爲委託:閉包

Expression<Func<int>> add = () => 1 + 2;
var func = add.Compile(); // 生產委託
var answer = func();      // 執行委託
Console.WriteLine(answer);

請注意,該委託類型基於表達式類型。 若是想要以強類型的方式使用委託對象,則必須知道返回類型和參數列表。 LambdaExpression.Compile() 方法返回 Delegate 類型。 必須將其轉換爲正確的委託類型,以便使任何編譯時工具檢查參數列表或返回類型。框架

執行和生存期

經過調用在調用 LambdaExpression.Compile() 時建立的委託來執行代碼。 能夠在上面進行查看,其中 add.Compile() 返回了一個委託。 經過調用 func() 調用該委託將執行代碼。函數

該委託表示表達式樹中的代碼。 能夠保留該委託的句柄並在稍後調用它。 不須要在每次想要執行表達式樹所表示的代碼時編譯表達式樹。 (請記住,表達式樹是不可變的,且在以後編譯同一表達式樹將建立執行相同代碼的委託。)工具

在此提醒你不要經過避免沒必要要的編譯調用嘗試建立用於提升性能的任何更復雜的緩存機制。 比較兩個任意的表達式樹,以肯定若是它們表示相同的算法,是否也會花費很長的時間來執行。 你可能會發現,經過避免對 LambdaExpression.Compile() 的任何額外調用所節省的計算時間將多於執行代碼(該代碼肯定可致使相同可執行代碼的兩個不一樣表達式樹)所花費的時間性能

注意事項

將 lambda 表達式編譯爲委託並調用該委託是可對錶達式樹執行的最簡單的操做之一。 可是,即便是執行這個簡單的操做,也存在一些必須注意的事項。

Lambda 表達式將對錶達式中引用的任何局部變量建立閉包。 必須保證做爲委託的一部分的任何變量在調用 Compile 的位置處和執行結果委託時可用。

通常狀況下,編譯器會確保這一點。 可是,若是表達式訪問實現 IDisposable 的變量,則代碼可能在表達式樹仍保留有對象時釋放該對象。

例如,此代碼工做正常,由於 int 不實現 IDisposable

private static Func<int, int> CreateBoundFunc()
{
    var constant = 5; // 常量由表達式樹捕獲
    Expression<Func<int, int>> expression = (b) => constant + b;
    var rVal = expression.Compile();
    return rVal;
}

委託已捕獲對局部變量 constant 的引用。 在稍後執行 CreateBoundFunc 返回的函數以後,可隨時訪問該變量。

可是,請考慮實現 IDisposable 的此(人爲設計的)類:

public class Resource : IDisposable
{
    private bool isDisposed = false;
    public int Argument
    {
        get
        {
            if (!isDisposed)
                return 5;
            else throw new ObjectDisposedException("Resource");
        }
    }

    public void Dispose()
    {
        isDisposed = true;
    }
}

若是將其用於以下所示的表達式中,則在執行 Resource.Argument 屬性引用的代碼時將出現 ObjectDisposedException

private static Func<int, int> CreateBoundResource()
{
    using (var constant = new Resource()) // 常量由表達式樹捕獲
    {
        Expression<Func<int, int>> expression = (b) => constant.Argument + b;
        var rVal = expression.Compile();
        return rVal;
    }
}

今後方法返回的委託已對釋放了的 constant 對象閉包。 (它已被釋放,由於它已在 using 語句中進行聲明。)

如今,在執行今後方法返回的委託時,將在執行時引起 ObjectDisposedException

出現表示編譯時構造的運行時錯誤確實很奇怪,但這是使用表達式樹時的正常現象。

此問題存在大量的排列,所以很難提供用於避免此問題的通常性指導。 定義表達式時,請謹慎訪問局部變量,且在建立可由公共 API 返回的表達式樹時,謹慎訪問當前對象(由 this 表示)中的狀態。

表達式中的代碼可能引用其餘程序集中的方法或屬性。 對錶達式進行定義、編譯或在調用結果委託時,該程序集必須可訪問。 在它不存在的狀況下,將遇到 ReferencedAssemblyNotFoundException

總結

能夠編譯表示 lambda 表達式的表達式樹,以建立可執行的委託。 這提供了一種機制,用於執行表達式樹所表示的代碼。

表達式樹表示會爲建立的任意給定構造執行的代碼。 只要編譯和執行代碼的環境匹配建立表達式的環境,則一切將按預期進行。 若是未按預期進行,那麼錯誤也是很容易預知的,而且將在使用表達式樹的任何代碼的第一個測試中捕獲這些錯誤。

 

相關文章
相關標籤/搜索