19.1.5泛型方法
在某些情況下,型別參數對於整個類別不是必需的,而只對特定方法內是必需的。經常,當創建一個接受泛型類型作為參數的方法時就是這樣。例如,當使用早先描述的Stack
一個通用的模式可能是在一行中壓入多個值,在一個單一的呼叫中寫一個方法這麼做也是很方便的。對於特定的建構類型,例如Stack
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
指定了一個或多個類型參數。類型參數可以在參數列表,返回類型和方法體之內被使用。一個泛型的PushMultiple方法將會是這樣。
void PushMultiple<T>(Stack<>T stack , params T[] values) { foreach(T value in values) stack.Push(value); }
使用這個泛型方法,你可以壓入多個項到任意Stack
Stack<int> stack = new Stack<int>(); PushMultiple<int>(stack , 1,2,3,4);
這個泛型PushMultiple方法比先前的版本更具有重用性,因為它可以工作在任何Stack
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.IEnumerator8742468051c85b06f0a0af9e3e506b5c所构造的类型。
可枚举接口是System.Collections.IEnumerable和由System.Collections.Generic.IEnumerable8742468051c85b06f0a0af9e3e506b5c构造的类型。
迭代器不是一种成员,它只是实现一个函数成员的方式,理解这点是很重要的。一个通过迭代器被实现的成员,可以被其他可能或不可能通过迭代器而被实现的成员覆盖和重载。
下面的Stack8742468051c85b06f0a0af9e3e506b5c类使用迭代器实现了它的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方法的存在使得Stack8742468051c85b06f0a0af9e3e506b5c成为一个可枚举类型,它使得Stack8742468051c85b06f0a0af9e3e506b5c的实例可被用在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方法,但经常有多种枚举方式,以及通过参数控制枚举的方法。在这种情况下,集合可以使用迭代器实现返回可枚举接口之一的属性和方法。例如,Stack8742468051c85b06f0a0af9e3e506b5c可能引入两个IEnumerable8742468051c85b06f0a0af9e3e506b5c类型的新属性,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#迭代器的设计可用于避免这些问题,从而以一种简单直观地方式实现健壮的枚举和枚举器。
以上就是C# 2.0 Specification(二)的内容,更多相关内容请关注PHP中文网(www.php.cn)!