在 C# 2.0 引入匿名委派、C# 3.0 引入 Lambda 表達式後,開發者常在衍生類別的方法中使用這些特性來捕獲外部變數(形成閉包)。然而,當匿名方法或 Lambda 表達式中透過 base 關鍵字呼叫基類方法時,編譯器會產生「無法驗證的程式碼」,並發出警告 CS1911。本文深入剖析其根本原因與解決方案。
public delegate void D();
public class Alpha
{
public virtual void Blah() { Console.WriteLine("Alpha.Blah"); }
}
public class Bravo : Alpha
{
public override void Blah()
{
Console.WriteLine("Bravo.Blah");
base.Blah(); // 正常呼叫基類方法
}
public void Charlie()
{
int x = 123;
D d = delegate
{
this.Blah(); // 呼叫自身覆寫方法
base.Blah(); // ⚠️ 這裡會產生警告 CS1911
Console.WriteLine(x);
};
d();
}
}
警告 CS1911:從匿名方法、Lambda 表達式、查詢運算式或迭代器透過 'base' 關鍵字存取成員 'Alpha.Blah()' 會導致程式碼無法驗證。請考慮將這類存取移至包含類型的輔助方法中。
當匿名方法捕獲區域變數(如 x)或 this 時,編譯器會產生一個私有巢狀類別(Compiler-Generated Class)來保存這些「逃逸變數」。原本的匿名方法會被提升為該巢狀類別的實例方法。
// 編譯器產生的偽代碼結構
private class __Locals
{
public int __x;
public Bravo __this;
public void __Method()
{
this.__this.Blah(); // 正常:呼叫 Bravo.Blah()
// 問題在這裡:
__nonvirtual__ ((Alpha)this.__this).Blah(); // 嘗試非虛擬呼叫 Alpha.Blah()
Console.WriteLine(this.__x);
}
}
關鍵問題在於:base.Blah() 在原始碼中意圖非虛擬呼叫基類 Alpha 的實作。但在巢狀類別 __Locals 中,base 指向的是 System.Object,而非 Alpha。編譯器為了實現此語意,必須產生 call IL 指令(而非 callvirt)直接呼叫 Alpha.Blah,但此時 this 指標指向的是 __Locals 實例,違反了 CLI 驗譼規則:「call 指令的 this 參數必須是呼叫方法的 this 參數」。
IL_0011: call instance void Alpha::Blah() // 此時 this 為 __Locals 實例,非 Alpha 實例
public void Charlie()
{
int x = 123;
D d = delegate
{
this.Blah();
CallBaseBlah(); // 改呼叫輔助方法
Console.WriteLine(x);
};
d();
}
private void CallBaseBlah()
{
base.Blah(); // 在一般方法中呼叫 base 安全且可驗證
}
若匿名方法僅捕獲 this(無其他區域變數),編譯器會優化為直接在原類別產生私有方法,不產生巢狀類別,此時 base 呼叫不會產生不可驗證程式碼(但編譯器仍會出於一致性發出警告)。
D d = () =>
{
this.Blah();
base.Blah(); // 同樣會產生警告
Console.WriteLine(x);
};
此問題源於 C# 閉包實作機制:匿名方法被提升至巢狀類別,導致 base 語意在 IL 層面無法正確表達。雖然程式碼可正常執行,但違反型別安全驗證。開發者應遵循編譯器建議,將 base 呼叫移至一般輔助方法中,確保程式碼可驗證、安全且可維護。