C#2.0匿名函数
C# 2.0中提供了通過delegate實(shí)現(xiàn)匿名函數(shù)功能,能有效地減少用戶記代碼工作,例如
以下為引用:
...
button1.Click += new EventHandler(button1_Click);
...
void button1_Click(Object sender, EventArgs e) {
// Do something, the button was clicked...
}
...
可以被簡化為直接使用匿名函數(shù)構(gòu)造,如
以下為引用:
...
button1.Click += delegate(Object sender, EventArgs e) {
// Do something, the button was clicked...
}
...
關(guān)于匿名函數(shù)的使用方法可以參考Jeffrey Richter的Working with Delegates Made Easier with C# 2.0一文。簡要說來就是C#編譯器自動(dòng)將匿名函數(shù)代碼轉(zhuǎn)移到一個(gè)自動(dòng)命名函數(shù)中,將原來需要用戶手工完成的工作自動(dòng)完成。例如構(gòu)造一個(gè)私有靜態(tài)函數(shù),如
以下為引用:
class AClass {
static void CallbackWithoutNewingADelegateObject() {
ThreadPool.QueueUserWorkItem(delegate(Object obj) { Console.WriteLine(obj); }, 5);
}
}
被編譯器自動(dòng)轉(zhuǎn)換為
以下為引用:
class AClass {
static void CallbackWithoutNewingADelegateObject() {
ThreadPool.QueueUserWorkItem(new WaitCallback(__AnonymousMethod$00000002), 5);
}
private static void __AnonymousMethod$00000002(Object obj) {
Console.WriteLine(obj);
}
}
而這里自動(dòng)生成的函數(shù)是否為static,編譯器根據(jù)使用此函數(shù)的地方是否static決定。這也是為什么C# 2.0規(guī)范里面禁止使用goto, break和continue語句從一個(gè)匿名方法里跳出,或從外面跳入其中的原因,因?yàn)樗麄兇a雖然寫在一個(gè)作用域里面,但實(shí)際上實(shí)現(xiàn)上并不在一起。
更方便的是編譯器可以根據(jù)匿名函數(shù)使用的情況,自動(dòng)判斷函數(shù)參數(shù),無需用戶在定義時(shí)指定,如
以下為引用:
button1.Click += delegate(Object sender, EventArgs e) { MessageBox.Show("The Button was clicked!"); };
在不使用參數(shù)時(shí),完全等價(jià)于
以下為引用:
button1.Click += delegate { MessageBox.Show("The Button was clicked!"); };
相對(duì)于匿名函數(shù)的實(shí)現(xiàn)來說,比較復(fù)雜的是匿名函數(shù)對(duì)于其父作用域中變量的使用及其實(shí)現(xiàn)。MS的Grant Ri在其blog上有一系列的討論文章。
Anonymous Methods, Part 1 of ?
Anonymous Methods, Part 2 of ?
Anonymous Method Part 2 answers
需要解決的問題有兩個(gè):一是不在一個(gè)變量作用域中的匿名函數(shù)如何訪問父函數(shù)和類的變量;二是匿名函數(shù)使用到的變量的生命周期必須與其綁定,而不能與父函數(shù)的調(diào)用生命周期綁定。這兩個(gè)問題使得C#編譯器選擇較為復(fù)雜的獨(dú)立類封裝方式實(shí)現(xiàn)匿名函數(shù)和相關(guān)變量生命周期的管理。
首先,匿名函數(shù)使用到的父函數(shù)中局部變量,無聊是引用類型還是值類型,都必須從棧變量轉(zhuǎn)換為堆變量,以便在其作用域外的匿名函數(shù)實(shí)現(xiàn)代碼可以訪問并控制生命周期。因?yàn)闂W兞康纳芷谂c其所有者函數(shù)是一致的,所有者函數(shù)退出后,其堆棧自動(dòng)恢復(fù)到調(diào)用函數(shù)前,也就無法完成變量生命周期與函數(shù)調(diào)用生命周期的解耦。
例如下面這個(gè)簡單的匿名函數(shù)中,使用了父函數(shù)的局部變量,雖然此匿名函數(shù)只在父函數(shù)里面使用,但C#編譯器還是使用獨(dú)立類對(duì)其使用到的變量進(jìn)行了包裝。
以下為引用:
delegate void Delegate1();
public void Method1()
{
int i=0;
Delegate1 d1 = delegate() { i++; };
d1();
}
自動(dòng)生成的包裝代碼類似如下
以下為引用:
delegate void Delegate1();
private sealed class __LocalsDisplayClass$00000002
{
public int i;
public void __AnonymousMethod$00000001()
{
this.i++;
}
};
public void Method1()
{
__LocalsDisplayClass$00000002 local1 = new __LocalsDisplayClass$00000002();
local1.i = 0;
Delegate1 d1 = new Delegate1(local1.__AnonymousMethod$00000001);
d1();
}
但對(duì)于有多個(gè)局部變量作用域的情況就比較復(fù)雜了,例如Grant Ri在其例子中給出的代碼
以下為引用:
delegate void NoArgs();
void SomeMethod()
{
NoArgs [] methods = new NoArgs[10];
int outer = 0;
for (int i = 0; i < 10; i++)
{
int inner = i;
methods[i] = delegate {
Console.WriteLine("outer = {0}", outer++);
Console.WriteLine("i = {0}", i);
Console.WriteLine("inner = {0}", ++inner);
};
methods[i]();
}
for (int j = 0; j < methods.Length; j++)
methods[j]();
}
就需要一個(gè)類封裝變量outer;一個(gè)類封裝變量i;另外一個(gè)類封裝inner和匿名函數(shù),并引用前面兩個(gè)封裝類的實(shí)例。因?yàn)樽兞縪uter、i和inner有著不同的作用域,呵呵。偽代碼如下:
以下為引用:
private sealed class __LocalsDisplayClass$00000008
{
public int outer;
};
private sealed class __LocalsDisplayClass$0000000a
{
public int i;
};
private sealed class __LocalsDisplayClass$0000000c
{
public int inner;
public __LocalsDisplayClass$00000008 $locals$00000009;
public __LocalsDisplayClass$0000000a $locals$0000000b;
public void __AnonymousMethod$00000007()
{
Console.WriteLine("outer = {0}", this.$locals$00000009.outer++);
Console.WriteLine("i = {0}", this.$locals$0000000b.i);
Console.WriteLine("inner = {0}", ++this.inner);
}
};
public void SomeMethod()
{
NoArgs [] methods = new NoArgs[10];
__LocalsDisplayClass$00000008 local1 = new __LocalsDisplayClass$00000008();
local1.outer = 0;
__LocalsDisplayClass$0000000a local2 = new __LocalsDisplayClass$0000000a();
local2.i = 0;
while(local2.i < 10)
{
__LocalsDisplayClass$0000000c local3 = new __LocalsDisplayClass$0000000c();
local3.$locals$00000009 = local1;
local3.$locals$0000000b = local2;
local3.inner = local1.i;
methods[local2.i] = new NoArgs(local3.__AnonymousMethod$00000007);
methods[local2.i]();
}
for (int j = 0; j < methods.Length; j++)
methods[j]();
}
總結(jié)其規(guī)律就是每個(gè)不同的局部變量作用域會(huì)有一個(gè)單獨(dú)的類進(jìn)行封裝,子作用域中如果使用到父作用域的局部變量,則子作用域的封裝類引用父作用域的封裝類。相同作用域的變量和匿名方法由封裝類綁定到一起,維護(hù)其一致的生命周期。
相對(duì)于MS較為復(fù)雜的實(shí)現(xiàn),Delphi.NET對(duì)嵌套函數(shù)則使用較為簡單的參數(shù)傳遞方式,因?yàn)榍短缀瘮?shù)沒有那么復(fù)雜的變量生命期管理要求,如
以下為引用:
procedure SayHello;
var
Name: string;
procedure Say;
begin
WriteLn(Name);
end;
begin
Name := 'Flier Lu';
Say;
end;
系統(tǒng)生成函數(shù)Say代碼時(shí),將使用到的上級(jí)變量如Name放入到一個(gè)自動(dòng)生成的類型($Unnamed1)中,然后作為函數(shù)參數(shù)傳遞給Say函數(shù),偽代碼類似
以下為引用:
type
$Unnamed1 = record
Name: string;
end;
procedure @1$SayHello$Say(var UnnamedParam: $Unnamed1);
begin
WriteLn(UnnamedParam.Name);
end;
procedure SayHello;
var
Name: string;
Unnamed1: $Unnamed1;
begin
Name := 'Flier Lu';
Unnamed1.Name := Name;
Say(Unnamed1);
end;?
?
以下為引用:
...
button1.Click += new EventHandler(button1_Click);
...
void button1_Click(Object sender, EventArgs e) {
// Do something, the button was clicked...
}
...
可以被簡化為直接使用匿名函數(shù)構(gòu)造,如
以下為引用:
...
button1.Click += delegate(Object sender, EventArgs e) {
// Do something, the button was clicked...
}
...
關(guān)于匿名函數(shù)的使用方法可以參考Jeffrey Richter的Working with Delegates Made Easier with C# 2.0一文。簡要說來就是C#編譯器自動(dòng)將匿名函數(shù)代碼轉(zhuǎn)移到一個(gè)自動(dòng)命名函數(shù)中,將原來需要用戶手工完成的工作自動(dòng)完成。例如構(gòu)造一個(gè)私有靜態(tài)函數(shù),如
以下為引用:
class AClass {
static void CallbackWithoutNewingADelegateObject() {
ThreadPool.QueueUserWorkItem(delegate(Object obj) { Console.WriteLine(obj); }, 5);
}
}
被編譯器自動(dòng)轉(zhuǎn)換為
以下為引用:
class AClass {
static void CallbackWithoutNewingADelegateObject() {
ThreadPool.QueueUserWorkItem(new WaitCallback(__AnonymousMethod$00000002), 5);
}
private static void __AnonymousMethod$00000002(Object obj) {
Console.WriteLine(obj);
}
}
而這里自動(dòng)生成的函數(shù)是否為static,編譯器根據(jù)使用此函數(shù)的地方是否static決定。這也是為什么C# 2.0規(guī)范里面禁止使用goto, break和continue語句從一個(gè)匿名方法里跳出,或從外面跳入其中的原因,因?yàn)樗麄兇a雖然寫在一個(gè)作用域里面,但實(shí)際上實(shí)現(xiàn)上并不在一起。
更方便的是編譯器可以根據(jù)匿名函數(shù)使用的情況,自動(dòng)判斷函數(shù)參數(shù),無需用戶在定義時(shí)指定,如
以下為引用:
button1.Click += delegate(Object sender, EventArgs e) { MessageBox.Show("The Button was clicked!"); };
在不使用參數(shù)時(shí),完全等價(jià)于
以下為引用:
button1.Click += delegate { MessageBox.Show("The Button was clicked!"); };
相對(duì)于匿名函數(shù)的實(shí)現(xiàn)來說,比較復(fù)雜的是匿名函數(shù)對(duì)于其父作用域中變量的使用及其實(shí)現(xiàn)。MS的Grant Ri在其blog上有一系列的討論文章。
Anonymous Methods, Part 1 of ?
Anonymous Methods, Part 2 of ?
Anonymous Method Part 2 answers
需要解決的問題有兩個(gè):一是不在一個(gè)變量作用域中的匿名函數(shù)如何訪問父函數(shù)和類的變量;二是匿名函數(shù)使用到的變量的生命周期必須與其綁定,而不能與父函數(shù)的調(diào)用生命周期綁定。這兩個(gè)問題使得C#編譯器選擇較為復(fù)雜的獨(dú)立類封裝方式實(shí)現(xiàn)匿名函數(shù)和相關(guān)變量生命周期的管理。
首先,匿名函數(shù)使用到的父函數(shù)中局部變量,無聊是引用類型還是值類型,都必須從棧變量轉(zhuǎn)換為堆變量,以便在其作用域外的匿名函數(shù)實(shí)現(xiàn)代碼可以訪問并控制生命周期。因?yàn)闂W兞康纳芷谂c其所有者函數(shù)是一致的,所有者函數(shù)退出后,其堆棧自動(dòng)恢復(fù)到調(diào)用函數(shù)前,也就無法完成變量生命周期與函數(shù)調(diào)用生命周期的解耦。
例如下面這個(gè)簡單的匿名函數(shù)中,使用了父函數(shù)的局部變量,雖然此匿名函數(shù)只在父函數(shù)里面使用,但C#編譯器還是使用獨(dú)立類對(duì)其使用到的變量進(jìn)行了包裝。
以下為引用:
delegate void Delegate1();
public void Method1()
{
int i=0;
Delegate1 d1 = delegate() { i++; };
d1();
}
自動(dòng)生成的包裝代碼類似如下
以下為引用:
delegate void Delegate1();
private sealed class __LocalsDisplayClass$00000002
{
public int i;
public void __AnonymousMethod$00000001()
{
this.i++;
}
};
public void Method1()
{
__LocalsDisplayClass$00000002 local1 = new __LocalsDisplayClass$00000002();
local1.i = 0;
Delegate1 d1 = new Delegate1(local1.__AnonymousMethod$00000001);
d1();
}
但對(duì)于有多個(gè)局部變量作用域的情況就比較復(fù)雜了,例如Grant Ri在其例子中給出的代碼
以下為引用:
delegate void NoArgs();
void SomeMethod()
{
NoArgs [] methods = new NoArgs[10];
int outer = 0;
for (int i = 0; i < 10; i++)
{
int inner = i;
methods[i] = delegate {
Console.WriteLine("outer = {0}", outer++);
Console.WriteLine("i = {0}", i);
Console.WriteLine("inner = {0}", ++inner);
};
methods[i]();
}
for (int j = 0; j < methods.Length; j++)
methods[j]();
}
就需要一個(gè)類封裝變量outer;一個(gè)類封裝變量i;另外一個(gè)類封裝inner和匿名函數(shù),并引用前面兩個(gè)封裝類的實(shí)例。因?yàn)樽兞縪uter、i和inner有著不同的作用域,呵呵。偽代碼如下:
以下為引用:
private sealed class __LocalsDisplayClass$00000008
{
public int outer;
};
private sealed class __LocalsDisplayClass$0000000a
{
public int i;
};
private sealed class __LocalsDisplayClass$0000000c
{
public int inner;
public __LocalsDisplayClass$00000008 $locals$00000009;
public __LocalsDisplayClass$0000000a $locals$0000000b;
public void __AnonymousMethod$00000007()
{
Console.WriteLine("outer = {0}", this.$locals$00000009.outer++);
Console.WriteLine("i = {0}", this.$locals$0000000b.i);
Console.WriteLine("inner = {0}", ++this.inner);
}
};
public void SomeMethod()
{
NoArgs [] methods = new NoArgs[10];
__LocalsDisplayClass$00000008 local1 = new __LocalsDisplayClass$00000008();
local1.outer = 0;
__LocalsDisplayClass$0000000a local2 = new __LocalsDisplayClass$0000000a();
local2.i = 0;
while(local2.i < 10)
{
__LocalsDisplayClass$0000000c local3 = new __LocalsDisplayClass$0000000c();
local3.$locals$00000009 = local1;
local3.$locals$0000000b = local2;
local3.inner = local1.i;
methods[local2.i] = new NoArgs(local3.__AnonymousMethod$00000007);
methods[local2.i]();
}
for (int j = 0; j < methods.Length; j++)
methods[j]();
}
總結(jié)其規(guī)律就是每個(gè)不同的局部變量作用域會(huì)有一個(gè)單獨(dú)的類進(jìn)行封裝,子作用域中如果使用到父作用域的局部變量,則子作用域的封裝類引用父作用域的封裝類。相同作用域的變量和匿名方法由封裝類綁定到一起,維護(hù)其一致的生命周期。
相對(duì)于MS較為復(fù)雜的實(shí)現(xiàn),Delphi.NET對(duì)嵌套函數(shù)則使用較為簡單的參數(shù)傳遞方式,因?yàn)榍短缀瘮?shù)沒有那么復(fù)雜的變量生命期管理要求,如
以下為引用:
procedure SayHello;
var
Name: string;
procedure Say;
begin
WriteLn(Name);
end;
begin
Name := 'Flier Lu';
Say;
end;
系統(tǒng)生成函數(shù)Say代碼時(shí),將使用到的上級(jí)變量如Name放入到一個(gè)自動(dòng)生成的類型($Unnamed1)中,然后作為函數(shù)參數(shù)傳遞給Say函數(shù),偽代碼類似
以下為引用:
type
$Unnamed1 = record
Name: string;
end;
procedure @1$SayHello$Say(var UnnamedParam: $Unnamed1);
begin
WriteLn(UnnamedParam.Name);
end;
procedure SayHello;
var
Name: string;
Unnamed1: $Unnamed1;
begin
Name := 'Flier Lu';
Unnamed1.Name := Name;
Say(Unnamed1);
end;?
?
總結(jié)
- 上一篇: 板房多少钱啊?
- 下一篇: 从DataView中生成Excel报表的