前面開篇花絮裡提到的是沒有熟思而做的優化帶來的後果,而下面要關注的問題就稍微復雜一些了。
考慮這段代碼片段:
引用
C#代碼
using System;
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( );
Console.WriteLine( x );
};
d( );
}
}
class Program {
// do nothing, just to make the compiler happy
// else we'd compiler with /target:library
public static void Main(string[] args) { }
}
用.Net Framework 3.5 Beta 2附帶的C#編譯器(csc.exe)編譯上面的代碼,會得到以下警告:
引用
Microsoft (R) Visual C# 2008 Compiler Beta 2 version 3.05.20706.1 for Microsoft (R) .Net Framework version 3.5
版權所有 (C) Microsoft Corporation。保留所有權利。
test1.cs(23,13): warning CS1911: 從匿名方法、lambda表達式、查詢表達式或迭代器通過“base”關鍵字訪問成員“Alpha.Blah()”會導致代碼無法驗證。請考慮將這種訪問移入針對包含類型的輔助方法中。
剛裝了.Net Framework 3.5的RTM,測試結果一樣。至於Mono 1.2.5.1更有趣,完全沒有報錯。
這裡有什麼問題呢?CharlIE()方法裡用this/base去訪問自身/基類的成員,不是很正常的麼。問題出在C#中應對閉包生成的代碼。
在C# 2.0中,引入了匿名delegate的概念,因而可以定義嵌套方法;在C# 3.0中,更進一步引入了Lambda Expression,同樣可以用於定義嵌套方法。這裡,嵌套的方法的作用域遵守詞法作用域,也就是說內部方法可以訪問外部包圍作用域的變量,包括外部的“this”。外部包圍作用域就對嵌套內部方法形成了“閉包”。
由於當一個嵌套方法生成(實例化)後,它的生命周期與它的外部方法不一定相同。它從外部環境中“捕獲”到的變量,就像是從外部“逃逸”出來了一樣。上面的例子中,CharlIE()方法裡x和this都成為了逃逸變量。
這些逃逸變量必須與嵌套方法的生命周期相同,即使外部方法已經返回也不能被立即銷毀;因此這些逃逸變量也不能在棧上分配。這樣,就需要為逃逸變量另外分配空間,常見的做法是在堆上分配。