(接上)
這篇文章是翻譯的微軟的技術文章.供學習c#的朋友參考,請勿用於商業目的。http://msdn.microsoft.com/vcsharp/team/language/default.aspx<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
19.1.5泛型方法
在某些情形下,類型參數對於整個類不是必需的,而只對特定方法內是必需的。經常,當創建一個接受泛型類型作爲參數的方法時就是這樣。例如,當使用早先描述的Stack<T>類時,一個通用的模式可能是在一行中壓入多個值,在一個單一的調用中寫一個方法這麼做也是很方便的。對於特定的構造類型,例如Stack<int>,這個方法看起來像這樣。
void PushMultiple(Stack<int> stack ,params int[] values)
{
foreach(int value in values)
stack.Push(value);
}
這個方法可以用於壓入多個int值到一個Stack<int>中。
Stack<int> stack = new Stack<int>();
PushMultiple(stack, 1,2, 3, 4);
然而,先前的方法只對於特定的構造類型Stack<int>有效。要讓它對於Stack<T>也起作用,方法必須被作爲泛型方法而編寫。泛型方法在方法的名字後面在“<”和“>”分界符之間指定了一個或多個類型參數。類型參數可以在參數列表,返回類型和方法體之內被使用。一個泛型的PushMultiple方法將會是這樣。
void PushMultiple<T>(Stack<>T stack , params T[] values)
{
foreach(T value in values) stack.Push(value);
}
使用這個泛型方法,你可以壓入多個項到任意Stack<T>中。當調用一個泛型方法時,類型參數值在方法調用的尖括號中被給定。例如
Stack<int> stack = new Stack<int>();
PushMultiple<int>(stack , 1,2,3,4);
這個泛型PushMultiple方法比先前的版本更具有重用性,因爲它可以工作在任何Stack<T>上,但似乎在調用的時候不太方便,因爲必須提供T作爲一個類型參數傳遞給方法。在許多情形下,編譯器使用一種稱爲類型推斷(type inferencing)處理,從傳遞給方法的其他參數推斷正確的類型參數。在先前的例子中,因爲第一個正式參數是Stack<int>類型,而後續的參數是int 類型,因此編譯器可以推斷類型參數值必須是int。由此,在調用泛型PushMultiple方法時可以不指定類型參數。
Stack<int> stack = new Stack<int>();
PushMultiple(stack , 1,2, 3, 4);
19.2匿名方法
事件句柄和其他回調函數經常需要通過專門的委託調用,從來都不是直接調用。雖然如此,我們還只能將事件句柄和回調函數的代碼,放在特定方法中,再顯式爲這個方法創建委託。相反,匿名方法(anonymous method)允許一個委託關聯的代碼被內聯的寫入使用委託的地方法,很方便的是這使得代碼對於委託的實例很直接。除了這種便利之外,匿名方法還共享了對本地語句包含的函數成員的訪問。爲了使命名方法達成共享(區別於匿名方法),需要手工創建輔助類,並將本地成員“提升(lifting)”爲類的域。
下面的例子展示了一個簡單的輸入表單,它包含一個列表框、一個文本框和一個按鈕。當按鈕被按下時,在文本框中一個包含文本的項就被添加到列表框。
class InputForm:Form
{
ListBox listBox;
TextBox textbox;
Button addButton;
pubic MyForm()
{
listBox = new ListBox(…);
textbox = new TextBox(…);
addButon = new Button(…);
addButton.Click += new EventHandler(AddClick);
}
void AddClick(object sender , EventArgs e)
{
listBox.Items.Add(textbox.Text);
}
}
即使作爲對按鈕的Click事件的響應只有唯一的一條語句需要執行。那條語句也必須放在一個具有完整的參數列表的單獨的方法中,並且還必須手工創建引用那個方法的EventHandler委託。使用匿名方法,事件處理代碼將變得相當簡潔。
class InputForm:Form
{
ListBox listBox;
TextBox textbox;
Button addButton;
pubic MyForm()
{
listBox = new ListBox(…);
textbox = new TextBox(…);
addButon = new Button(…);
addButton.Click +=delegate{
listBox.Items.Add(textBox.Text.);
}
}
匿名方法由關鍵詞delegate和一個可選的參數列表,以及一個封閉在“{”和“}”分界符中的語句組成。先前的例子中匿名方法沒有使用由委託所提供的參數,所以便省略了參數列表。如果要訪問參數,匿名方法可以包含一個參數列表。
addButton.Click += delegate(object sender , EventArgs e){
MessageBox.Show(((Button)sender).Text);
};
在前面的例子中,將會發生一次從匿名方法到EventHandler委託類型(Click事件的類型)的隱式轉換。這種隱式轉換是可能的,因爲參數列表和委託類型的返回值與匿名方法是兼容的。關於兼容性的確切規則如下:
- 如果下列之一成立,那麼委託的參數列表與匿名方法是兼容的。
- 匿名方法沒有參數列表,並且委託沒有out 參數。
- 匿名方法包含的參數列表與委託的參數在數量、類型和修飾符上是精確匹配的。
- 如果下列之一成立,那麼委託的返回類型與匿名方法兼容。
- 委託的返回類型是void,匿名方法沒有返回語句,或者只有不帶表達式的return 語句。
- 委託的返回類型不是void ,並且在匿名方法中,所有return 語句相關的表達式可以被隱式轉換到委託的類型。
在委託類型的隱式轉換髮生以前,委託的參數列表和返回類型二者都必須與匿名方法兼容。
下面的例子使用匿名方法編寫了“內聯”函數。匿名方法被作爲Function委託類型而傳遞。
using System;
delegate double Function(double x);
class Test
{
static double[] Apply(double[] a ,Function f)
{
double[] result = new double[a.Length];
for(int i=0;i<a.Length;i++) result[i] = f(a[i]);
return result;
}
static double[] MultiplyAllBy(double[] a, double factor)
{
return Apply(a, delegate(double x){return x*factor;})
}
static void <?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />Main()
{
double[] a = {0.0,0.5,1.0};
double[] squares = Apply(a, delegate(double x){return x*x});
double[] doubles = MultiplyAllBy(a , 2.0);
}
}
Apply方法適用一個 double[]元素的給定的Function,並返回一個double[]作爲結果。在Main方法中,傳遞給Apply的第二個參數是一個匿名方法,它與Fucntion委託類型兼容。該匿名方法只是返回參數的平方,而Apply調用的結果是一個double[] ,在a中包含了值的平方。
MultiplyAllBy方法返回一個由一個給定factor(因數)的在參數數組a中的每個值相乘而創建的double[] 。要得到結果,MultiplyAllBy調用了Apply方法,並傳給它一個匿名方法(在參數上乘以因數factor)。
如果一個本地變量或參數的作用域包括了匿名方法,則該變量和參數被稱爲匿名方法的外部變量(outer variable)。在MultiplyAllBy方法中,a和factor是傳遞給Apply的匿名方法的外部變量,因爲匿名方法引用了factor,factor被匿名方法所捕獲(capture)。通常,局部變量的生存期被限制在它所關聯的塊或語句的執行區。然而,被捕獲的外部變量將一直存活到委託所引用的匿名方法可以被垃圾回收爲止。
19.2.1方法組轉換
如前面所描述的,匿名方法可以被隱式地轉換到與之兼容的委託類型。對於一個方法組,C#2.0允許這種相同類型的轉換,即在幾乎任何情況下都不需要顯式地實例化委託。例如,下面的語句
addButton.Click += new EventHandler(AddClick);
Apply(a , new Function(Math.Sin));
可以被如下語句代替.
addButton.Click += AddClick;
Apply(a , Math.Sin);
當使用這種簡短的形式時,編譯器將自動推斷哪一個委託類型需要實例化,但其最後的效果與較長的表達形式是一樣的。
19.3迭代器
C#的foreach語句被用於迭代一個可枚舉(enumerable)集合的所有元素。爲了可以被枚舉,集合必須具有一個無參數GetEnumerator方法,它返回一個enumertor(枚舉器)。一般情況下,枚舉器是很難實現的,但這個問題使用迭代器就大大地簡化了。
迭代器是一個產生值的有序序列的語句塊。迭代器不同於有一個或多個yield語句存在的常規語句塊。
- yield return語句產生迭代的下一個值。
- yield break語句指明迭代已經完成。
只要函數成員的返回類型是枚舉器接口(enumerator interface)或可枚舉接口(enumerable interface)之一,迭代器就可以被用作函數體。
- 枚舉器接口是System.Collections.IEnumerator和由Sysetm.Collections.Generic.IEnumerator<T>所構造的類型。
- 可枚舉接口是System.Collections.IEnumerable和由System.Collections.Generic.IEnumerable<T>構造的類型。
迭代器不是一種成員,它只是實現一個函數成員的方式,理解這點是很重要的。一個通過迭代器被實現的成員,可以被其他可能或不可能通過迭代器而被實現的成員覆蓋和重載。
下面的Stack<T>類使用迭代器實現了它的GetEnumerator方法。這個迭代器依序枚舉了堆棧從頂到底的所有元素。
using System.Collections.Generic;
public class Stack<T>:IEnumerable<T>
{
T[] items;
int count;
public void Push(T data){…}
public T Pop(){…}
public IEnumerator<T> GetEnumerator()
{
for(int i =count-1;i>=0;--i){
yield return items[i];
}
}
}
GetEnumerator方法的存在使得Stack<T>成爲一個可枚舉類型,它使得Stack<T>的實例可被用在foreach語句中。下面的例子壓入從0到9 的值到一個整數堆棧中,並且使用一個foreach循環依序顯示從堆棧頂到底的所有值。
using System;
class Test
{
static void Main()
{
Stack<int> stack = new Stack<int>();
for(int i=0;i<10;i++) stack.Push(i);
foreach(int i in stack) Console.Write(“{0}”,i);
Console.WriteLine();
}
}
例子的輸出入下:
9 8 7 6 5 4 3 2 1 0
foreach語句隱式地調用了集合的無參數GetEnumerator方法以獲得一個枚舉器。由集合所定義的只能有一個這樣的無參數 GetEnumerator方法,但經常有多種枚舉方式,以及通過參數控制枚舉的方法。在這種情況下,集合可以使用迭代器實現返回可枚舉接口之一的屬性和方法。例如,Stack<T>可能引入兩個IEnumerable<T>類型的新屬性,TopToBottom和BottomToTop。
using System.Collections.Generic;
public class Stack<T>: IEnumerable<T>
{
T[] items;
int count;
public void Push(T data){…}
public T Pop()P{…}
public IEnumerator<T> GetEnumerator()
{
for(int i= count-1;i>=0;--i)
{
yield return items[i];
}
}
public IEnumerable<T> TopBottom{
get{
return this;
}
}
public IEnumerable<T> BottomToTop{
get{
for(int I = 0;i<count;i++)
{
yield return items[i];
}
}
}
}
TopToBottom屬性的get訪問器只是返回this,因爲堆棧自身是可枚舉的。BottomToTop屬性返回一個使用C#迭代器實現的枚舉。下面的例子展示了,屬性如何用於枚舉堆棧元素。
using System;
class Test
{
static void Main()
{
Stack<int> stack = new Stack<int>();
for(int i = 0 ;i<10 ;i++) stack.Push(i);
for(int i in stack..TopToBottom) Console.Write(“{0}”,i);
Console.WriteLine();
for(int i in stack..BottomToTop) Console.Write(“{0}”,i);
Console.WriteLine();
}
}
當然,這些屬性同樣也可以在foreach語句之外使用。下面的例子將調用屬性的結果傳遞給了一個單獨的Print方法。該例子也展示了一個用作FromToBy接受參數的方法體的迭代器。
using System;
using System.Collections.Generic;
class Test
{
static void Print(IEnumerable<int> collection)
{
foreach(int i in collection) Console.Write(“{0}”,i);
Console.WriteLine();
}
static IEnumerable<int> FromToBy(int from ,int to , int by)
{
for(int i =from ;i<=to ;i +=by)
{
yield return I;
}
}
static void Main()
{
Stack<int> stack = new Stack<int>();
for(int i= 0 ;i<10;i ++) stack.Push(i);
Print(stack.TopToBottom);
Print(stack.BottomToTop);
Print(FromToBy(10,20,2));
}
}
該例子的輸出如下。
9 8 7 6 5 4 3 2 1 0
0 1 2 3 4 5 6 7 8 9
10 12 14 16 18 20
泛型和非泛型可枚舉接口包含一個單一的成員,一個不接受參數的GetEnumerator方法 ,它一個枚舉器接口。一個枚舉充當一個枚舉器工廠。每當調用了一個正確地實現了可枚舉接口的類的GetEnumerator方法時,都會產生一個獨立的枚舉器。假定枚舉的內部狀態在兩次調用GetEnumerator之間沒有改變,返回的枚舉器應該產生相同集合相同順序的枚舉值。在下面的例子中,這點應該保持,即使枚舉的生存期發生交疊。
using System;
using System.Collections.Generic;
class Test
{
static IEnumerable<int> FromTo(int from , int to)
{
while(from<=to) yield return from++;
}
static void Main()
{
IEnumerable<int> e = FromTo(1,10);
foreach(int x in e)
{
foreach(int y in e)
{
Console.WriteLine(“{0,3}”,x*y);
}
Console.WriteLine();
}
}
}
先前的代碼打印了整數1到10 的乘法表。注意,FromTo方法只被調用了一次用來產生枚舉e。然而,e.GetEnumerator()被調用了多次(通過foreach語句),以產生多個等價的枚舉器。這些枚舉器都封裝了在FromTo聲明中指定的迭代器代碼。注意迭代器代碼修改了from參數。
不過,枚舉器是獨立地運作的,因爲每個枚舉器都給出 from 和to它自己的拷貝。枚舉器之間過渡狀態的共享是衆多細微的瑕疵之一,當實現枚舉和枚舉器時應該避免。C#迭代器的設計可用於避免這些問題,從而以一種簡單直觀地方式實現健壯的枚舉和枚舉器。