21.7委托實例相等性
如下規則適用由匿名方法委托實例的相等運算符(§7.9.8)和object.Equals方法產生的結果。
l 當委托實例是由具有相同被捕獲外部變量集合的語義相同的匿名方法表達式計算而產生時,可以說(但不是必須)它們相等。
l 當委托實例由具有語義不同的匿名方法表達式,或具有不同的被捕獲外部變量集合時,它們決不相等。
21.8明確賦值
匿名方法參數的明確賦值狀態與命名方法是相同的。也就是,引用參數和值參數被明確的賦初值,而輸出參數不用賦初值。並且,輸出參數在匿名方法正常返回之前必須被明確賦值(§5.1.6)。
當控制轉換到匿名方法表達式的程序塊時,對外部變量v的明確賦值狀態,與在匿名方法表達式之前的v的明確賦值狀態是相同的。也就是,外部變量的明確賦值將從匿名方法表達式上下文被繼承。在匿名方法程序塊內,明確賦值將和在普通程序塊內一樣而得到演繹(§5.3.3)。
在匿名方法表達式之後的變量v的明確賦值狀態與在匿名方法表達式之前它的明確賦值狀態相同。
例如
delegate bool Filter(int i);
void F() {
int max;
// 錯誤,max沒有明確賦值
Filter f = delegate(int n) { return n < max; }
max = 5;
DoWork(f);
}
將產生一個編譯時錯誤,因為max沒有在匿名方法聲明的地方明確賦值。示例
delegate void D();
void F() {
int n;
D d = delegate { n = 1; };
d();
//錯誤,n沒有明確賦值
Console.WriteLine(n);
}
也將產生一個編譯時錯誤,因為匿名方法內n的賦值,對於該匿名方法外部n的明確賦值狀態沒有效果。
21.9方法組轉換
與在§21.3中描述的隱式匿名方法轉換相似,也存在從方法組(§7.1)到兼容的委托類型的隱式轉換。
對於給定的方法組E和委托類型D,如果允許new D(E)形式的委托創建表達式(§7.5.10.3 和 §20.9.6),那麼就存在從E到D的隱式轉換,並且轉換的結果恰好等價於new D(E)。
在以下示例中
using System;
using System.Windows.Forms;
class AlertDialog
{
Label message = new Label();
Button okButton = new Button();
Button cancelButton = new Button();`
public AlertDialog() {
okButton.Click += new EventHandler(OkClick);
cancelButton.Click += new EventHandler(CancelClick);
...
}
void OkClick(object sender, EventArgs e) {
...
}
void CancelClick(object sender, EventArgs e) {
...
}
}
構造函數用new創建了兩個委托實例。隱式方法組轉換允許將之簡化為
public AlertDialog() {
okButton.Click += OkClick;
cancelButton.Click += CancelClick;
...
}
對於所有其他隱式和顯式的轉換,轉換運算符可以用於顯式地執行一個特定的轉換。為此,示例
object obj = new EventHandler(myDialog.OkClick);
可被代替寫成如下的樣子。
object obj = (EventHandler)myDialog.OkClick;
方法組合匿名方法表達式可以影響重載決策(overload resolution),但它們並不參與類型推斷。請參見§20.6.4獲取更詳細的信息。
21.10實現例子
本節以標准C#的構件形式描述匿名方法的可能實現。在這裡描述的實現基於Microsoft C#編譯器所采用的相同原則,但它決不是強制性的或唯一可能的實現。
本節的後面部分給出了幾個示例代碼,它包含了具有不同特性的匿名方法。對於每個例子,我們將提供使用唯一標准C#構件的代碼的對應轉換。在這些例子中,標識符D假定表示如下委托類型。
public delegate void D();
匿名方法的最簡形式就是沒有捕獲外部變量的那個。
class Test
{
static void F() {
D d = delegate { Console.WriteLine("test"); };
}
}
這段代碼可被轉換到一個引用編譯器生成的靜態方法的委托實例,而匿名方法的代碼將會放入到該靜態方法中。、
class Test
{
static void F() {
D d = new D(__Method1);
}
static void __Method1() {
Console.WriteLine("test");
}
}
在下面的示例中,匿名方法引用this的實例成員。
class Test
{
int x;
void F() {
D d = delegate { Console.WriteLine(x); };
}
}
this可以被轉換到由編譯器生成的包含匿名方法代碼的實例方法。
class Test
{
int x;
void F() {
D d = new D(__Method1);
}
void __Method1() {
Console.WriteLine(x);
}
}
在這個例子中,匿名方法捕獲了一個局部變量。
class Test
{
void F() {
int y = 123;
D d = delegate { Console.WriteLine(y); };
}
}
該局部變量的生存期現在至少必須延長到匿名方法委托的生存期為止。這可以通過將局部變量“提升(lifting)”為編譯器生成的(compiler-generated)類的字段來完成。局部變量的實例化對應於創建一個編譯器生成的類的實例,而訪問局部變量將對應於訪問編譯器生成的類實例的一個字段。並且,匿名方法將成為編譯器生成類的實例方法。
class Test
{
void F() {
__locals1 = new __Locals1();
__locals1.y = 123;
D d = new D(__locals1.__Method1);
}
class __Locals1
{
public int y;
public void __Method1() {
Console.WriteLine(y);
}
}
}
最後,如下匿名方法將捕獲this,以及具有不同生存期的兩個局部變量。
class Test
{
int x;
void F() {
int y = 123;
for (int i = 0; i < 10; i++) {
int z = i * 2;
D d = delegate { Console.WriteLine(x + y + z); };
}
}
}
在這裡,編譯器將為每個語句塊生成類,在這些語句塊中局部變量將被捕獲,而在不同塊中的局部變量將會有獨立的生存期。
__Locals2的實例,編譯器為內部語句塊生成的類,包含局部變量z和引用__Locals1實例的字段。__Locals1的實例,編譯器為外部語句塊生成的類,包含局部變量y和引用封閉函數成員的this的字段。通過這些數據結構,你可以通過__Locals2的一個實例到達所有被捕獲的局部變量,並且匿名方法的代碼可以作為那個類的實例方法而實現。
class Test
{
void F() {
__locals1 = new __Locals1();
__locals1.__this = this;
__locals1.y = 123;
for (int i = 0; i < 10; i++) {
__locals2 = new __Locals2();
__locals2.__locals1 = __locals1;
__locals2.z = i * 2;
D d = new D(__locals2.__Method1);
}
}
class __Locals1
{
public Test __this;
public int y;
}
class __Locals2
{
public __Locals1 __locals1;
public int z;
public void __Method1() {
Console.WriteLine(__locals1.__this.x + __locals1.y + z);
}
}
}