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

C# 匿名委派呼叫基類方法導致無法驗證程式碼的深度解析 (四)

     編輯:關於C語言

前言

在 C# 中,當派生類別的方法內使用匿名委派(或 Lambda 運算式)呼叫基類別的方法時,編譯器產生的 IL 代碼可能會導致「無法驗證」的錯誤。這是一個經典且深入的問題,涉及閉包的實作機制、編譯器優化策略以及 .NET 執行環境的驗證規則。本文將延續前幾篇的討論,深入剖析這一現象的成因、編譯器的優化行為以及 Mono 與 .NET Framework 的差異。

錯誤現象與 IL 分析

當使用 PEVerify 工具驗證編譯後的組件時,會出現如下錯誤:

Microsoft (R) .Net Framework PE Verifier. Version 3.5.20706.1
Copyright (c) Microsoft Corporation. All rights reserved.
[IL]: Error: [F:\FX\share\testClosure.exe : Bravo+<>c__CompilerGenerated0::<CharlIE>c__1][offset 0x00000011] The 'this' parameter to the call must be the calling method's 'this' parameter.
1 Error Verifying testClosure.exe

對應的 IL 代碼為:

IL_0011: call instance void Alpha::Blah()

這行 IL 正是原始碼中 base.Blah() 呼叫所生成的指令。問題的核心在於:匿名委派被編譯器提升到一個編譯器生成的私有巢狀類別中,而在該巢狀類別的方法中呼叫 call instance void Alpha::Blah() 時,傳遞的 this 指標並非該巢狀類別的實例,而是外部類別 Bravo 的實例。這違反了 CLI 驗證規則:call 指令要求傳遞的 this 必須是當前方法所屬類別的實例。

C# 3.0 Lambda 運算式的同樣行為

在 C# 3.0 中使用 Lambda 運算式時,情況完全相同:

public void CharlIE() {
    int x = 123;
    D d = () => {
        this.Blah();
        base.Blah();
        Console.WriteLine(x);
    };
    d();
}

編譯器同樣會生成不可驗證的代碼,並發出警告 CS1911。

編譯器優化:當只有 this 逃逸時

有趣的是,如果移除區域變數 x,使得匿名委派捕獲的唯一外部變數只有 this:

public void CharlIE() {
    D d = delegate {
        this.Blah();
        base.Blah();
    };
    d();
}

.NET Framework 的 C# 編譯器會進行優化:它不再生成私有巢狀類別,而是將匿名委派直接生成為外部類別 Bravo 的一個私有成員方法。偽代碼如下:

public class Bravo : Alpha {
    public override void Blah() {
        Console.WriteLine("Bravo.Blah");
        base.Blah();
    }

    // compiler generated method
    private void __method() {
        this.Blah();
        // 非虛擬呼叫基類方法,C# 中無直接語法,此處用 __nonvirtual__ 表示
        __nonvirtual__ ((Alpha)this).Blah();
    }

    public void CharlIE() {
        D d = new D(this.__method);
        d();
    }
}

在這種情況下,編譯器生成的代碼是可驗證的,因為 __method 位於 Bravo 類別中,呼叫 base.Blah() 時的 this 正是 Bravo 的實例,符合驗證規則。然而,為了行為一致性,編譯器仍會發出 CS1911 警告。

Mono 的不同行為

Mono 編譯器並未實作這項優化,即使只有 this 逃逸,它仍會生成巢狀類別,導致同樣的不可驗證代碼,且不發出任何警告。這反映了 Mono 為了相容性選擇模仿 .NET Framework 的一般行為,而非採用相同的優化策略。

總結

這一系列文章詳細闡述了 C# 匿名委派捕獲基類方法呼叫時的閉包實作細節、編譯器生成代碼的驗證問題、優化條件以及跨平台差異。理解這些機制有助於開發者編寫更安全、可驗證的代碼,並在必要時通過輔助方法規避警告。

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