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 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)[/b]。通常,局部變數的生存期被限制在它所關聯的塊或語句的執行區。然而,被捕獲的外部變數將一直存活到委託所引用的匿名方法可以被記憶體回收為止。
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 00 1 2 3 4 5 6 7 8 910 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#迭代器的設計可用於避免這些問題,從而以一種簡單直觀地方式實現健壯的枚舉和列舉程式。
以上就是C# 2.0 Specification(二)的內容,更多相關內容請關注topic.alibabacloud.com(www.php.cn)!