(接上)
这篇文章是翻译的微软的技术文章 .供学习c#的朋友参考,请勿用于商业目的。 http://msdn.microsoft.com/vcsharp/team/language/default.aspx
19.1.5 泛型方法
在某些情形下,类型参数对于整个类不是必需的,而只对特定方法内是必需的。经常,当创建一个接受泛型类型作为参数的方法时就是这样。例如,当使用早先描述的 Stack
1<t> 类时,一个通用的模式可能是在一行中压入多个值,在一个单一的调用中写一个方法这么做也是很方便的。对于特定的构造类型,例如 Stack<int> ,这个方法看起来像这样 。
2
3void PushMultiple(Stack<int> stack ,params int[] values)
4
5{
6
7foreach(int value in values)
8
9stack.Push(value);
10
11}
12
13
14
15
16这个方法可以用于压入多个 int 值到一个 Stack<int> 中。
17
18Stack<int> stack = new Stack<int>();
19
20PushMultiple(stack, 1,2, 3, 4);
21
22然而,先前的方法只对于特定的构造类型 Stack<int> 有效。要让它对于 Stack<t> 也起作用,方法必须被作为泛型方法而编写。泛型方法在方法的名字后面在“ <”和“>”分界符之间指定了一个或多个类型参数。类型参数可以在参数列表,返回类型和方法体之内被使用。一个泛型的 PushMultiple 方法将会是这样。
23
24void PushMultiple<t>(Stack<>T stack , params T[] values)
25
26{
27
28foreach(T value in values) stack.Push(value);
29
30}
31
32使用这个泛型方法,你可以压入多个项到任意 Stack<t> 中。当调用一个泛型方法时,类型参数值在方法调用的尖括号中被给定。例如
33
34Stack<int> stack = new Stack<int>();
35
36PushMultiple<int>(stack , 1,2,3,4);
37
38这个泛型 PushMultiple 方法比先前的版本更具有重用性,因为它可以工作在任何 Stack<t> 上,但似乎在调用的时候不太方便,因为必须提供 T作为一个类型参数传递给方法。在许多情形下,编译器使用一种称为 ** 类型推断( ** ** type inferencing ** ** ) ** 处理,从传递给方法的其他参数推断正确的类型参数。在先前的例子中,因为第一个正式参数是 Stack<int> 类型,而后续的参数是 int 类型,因此编译器可以推断类型参数值必须是 int 。由此,在调用泛型 PushMultiple 方法时可以不指定类型参数。
39
40Stack<int> stack = new Stack<int>();
41
42PushMultiple(stack , 1,2, 3, 4);
43
44## 19.2 匿名方法
45
46事件句柄和其他回调函数经常需要通过专门的委托调用,从来都不是直接调用。虽然如此,我们还只能将事件句柄和回调函数的代码,放在特定方法中,再显式为这个方法创建委托。相反, ** 匿名方法( ** ** anonymous method ** ** ) ** 允许一个委托关联的代码被内联的写入使用委托的地方法,很方便的是这使得代码对于委托的实例很直接。除了这种便利之外,匿名方法还共享了对本地语句包含的函数成员的访问。为了使命名方法达成共享(区别于匿名方法),需要手工创建辅助类,并将本地成员“提升( lifting )”为类的域。
47
48
49
50
51下面的例子展示了一个简单的输入表单,它包含一个列表框、一个文本框和一个按钮。当按钮被按下时,在文本框中一个包含文本的项就被添加到列表框。
52
53class InputForm:Form
54
55{
56
57ListBox listBox;
58
59TextBox textbox;
60
61Button addButton;
62
63pubic MyForm()
64
65{
66
67listBox = new ListBox(…);
68
69textbox = new TextBox(…);
70
71addButon = new Button(…);
72
73addButton.Click += new EventHandler(AddClick);
74
75}
76
77void AddClick(object sender , EventArgs e)
78
79{
80
81listBox.Items.Add(textbox.Text);
82
83}
84
85}
86
87即使作为对按钮的 Click 事件的响应只有唯一的一条语句需要执行。那条语句也必须放在一个具有完整的参数列表的单独的方法中,并且还必须手工创建引用那个方法的 EventHandler 委托。使用匿名方法,事件处理代码将变得相当简洁。
88
89class InputForm:Form
90
91{
92
93ListBox listBox;
94
95TextBox textbox;
96
97Button addButton;
98
99pubic MyForm()
100
101{
102
103listBox = new ListBox(…);
104
105textbox = new TextBox(…);
106
107addButon = new Button(…);
108
109addButton.Click +=delegate{
110
111listBox.Items.Add(textBox.Text.);
112
113}
114
115}
116
117匿名方法由关键词 delegate 和一个可选的参数列表,以及一个封闭在“ {”和“}”分界符中的语句组成。先前的例子中匿名方法没有使用由委托所提供的参数,所以便省略了参数列表。如果要访问参数,匿名方法可以包含一个参数列表。
118
119
120
121
122addButton.Click += delegate(object sender , EventArgs e){
123
124MessageBox.Show(((Button)sender).Text);
125
126};
127
128在前面的例子中,将会发生一次从匿名方法到 EventHandler 委托类型( Click 事件的类型)的隐式转换。这种隐式转换是可能的,因为参数列表和委托类型的返回值与匿名方法是兼容的。关于兼容性的确切规则如下:
129
130 * 如果下列之一成立,那么委托的参数列表与匿名方法是兼容的。
131
132
133
134\- 匿名方法没有参数列表,并且委托没有 out 参数。
135
136\- 匿名方法包含的参数列表与委托的参数在数量、类型和修饰符上是精确匹配的。
137
138 * 如果下列之一成立,那么委托的返回类型与匿名方法兼容。
139
140
141
142\- 委托的返回类型是 void ,匿名方法没有返回语句,或者只有不带表达式的 return 语句。
143
144\- 委托的返回类型不是 void ,并且在匿名方法中,所有 return 语句相关的表达式可以被隐式转换到委托的类型。
145
146在委托类型的隐式转换发生以前,委托的参数列表和返回类型二者都必须与匿名方法兼容。
147
148下面的例子使用匿名方法编写了“内联”函数。匿名方法被作为 Function 委托类型而传递。
149
150using System;
151
152delegate double Function(double x);
153
154class Test
155
156{
157
158static double[] Apply(double[] a ,Function f)
159
160{
161
162double[] result = new double[a.Length];
163
164for(int i=0;i<a.length;i++) ##="" ###="" ()="" *="" **="" **)**="" +="AddClick;" ,="" .="" 19.2.1="" 19.3="" 2.0);="" a="{0.0,0.5,1.0};" a,="" addbutton.click="" apply="" apply(a="" apply(a,="" a中的每个值相乘而创建的="" a和factor是传递给apply的匿名方法的外部变量,因为匿名方法引用了="" break="" c#2.0允许这种相同类型的转换,即在几乎任何情况下都不需要显式地实例化委托。例如,下面的语句="" c#的="" capture="" delegate(double="" double="" double[]="" doubles="MultiplyAllBy(a" enumerable="" enumerator="" enumertor="" eventhandler(addclick);="" factor="" factor)="" foreach="" fucntion="" function="" function(math.sin));="" getenumerator="" interface="" main="" math.sin);="" multiplyallby="" multiplyallby(double[]="" new="" outer="" r="" result;="" result[i]="f(a[i]);" return="" squares="Apply(a," static="" sysetm.collections.generic.ienumerator<t="" system.collections.ienumerato="" variable="" void="" x){return="" x*factor;})="" x*x});="" yield="" yield语句存在的常规语句块。="" {="" }="" 。一般情况下,枚举器是很难实现的,但这个问题使用迭代器就大大地简化了。="" 。在="" 。要得到结果,="" 。通常,局部变量的生存期被限制在它所关联的块或语句的执行区。然而,被捕获的外部变量将一直存活到委托所引用的匿名方法可以被垃圾回收为止。="" 作为结果。在="" 元素的给定的="" 只要函数成员的返回类型是枚举器接口(="" 可以被如下语句代替="" 可枚举(="" 和由="" 外部变量(="" 如前面所描述的,匿名方法可以被隐式地转换到与之兼容的委托类型。对于一个方法组,="" 如果一个本地变量或参数的作用域包括了匿名方法,则该变量和参数被称为匿名方法的="" 委托类型兼容。该匿名方法只是返回参数的平方,而="" 当使用这种简短的形式时,编译器将自动推断哪一个委托类型需要实例化,但其最后的效果与较长的表达形式是一样的。="" 捕获(="" 方法中,="" 方法中,传递给="" 方法组转换="" 方法返回一个由一个给定="" 方法适用一个="" 方法,它返回一个="" 方法,并传给它一个匿名方法(在参数上乘以因数="" 枚举器接口是="" 的第二个参数是一个匿名方法,它与="" 被匿名方法所="" 语句产生迭代的下一个值。="" 语句指明迭代已经完成。="" 语句被用于迭代一个="" 调用了="" 调用的结果是一个="" 迭代器="" 迭代器是一个产生值的有序序列的语句块。迭代器不同于有一个或多个="" 集合的所有元素。为了可以被枚举,集合必须具有一个无参数="" (因数)的在参数数组="" (枚举器)="" )="" )。="" )之一,迭代器就可以被用作函数体。="" )或可枚举接口(="" ,="" ,在a中包含了值的平方。="" ,并返回一个=""> 所构造的类型。
165 * 可枚举接口是 System.Collections.IEnumerable 和由 System.Collections.Generic.IEnumerable<t> 构造的类型。
166
167
168
169迭代器不是一种成 员, 它只是实现一个函数成员的方式,理解这点是很重要的。一个通过迭代器被实现的成员,可以被其他可能或不可能通过迭代器而被实现的成员覆盖和重载。
170
171下面 的 Stack<t> 类使用迭代器实现了它的 GetEnumerator 方法。这个迭代器依序枚举了堆栈从顶到底的所有元素。
172
173using System.Collections.Generic;
174
175public class Stack<t>:IEnumerable<t>
176
177{
178
179T[] items;
180
181int count;
182
183public void Push(T data){…}
184
185public T Pop(){…}
186
187public IEnumerator<t> GetEnumerator()
188
189{
190
191for(int i =count-1;i>=0;--i){
192
193yield return items[i];
194
195}
196
197}
198
199}
200
201
202
203
204GetEnumerator 方法的存在使得 Stack<t> 成为一个可枚举类型,它使得 Stack<t> 的实例可被用在 foreach 语句中。下面的例子压入从 0到9 的值到一个整数堆栈中,并且使用一个 foreach 循环依序显示从堆栈顶到底的所有值。
205
206using System;
207
208class Test
209
210{
211
212static void Main ()
213
214{
215
216Stack<int> stack = new Stack<int>();
217
218for(int i=0;i<10;i++) stack.Push(i);
219
220foreach(int i in stack) Console.Write(“{0}”,i);
221
222Console.WriteLine();
223
224}
225
226}
227
228例子的输出入下:
229
2309 8 7 6 5 4 3 2 1 0
231
232foreach 语句隐式地调用了集合的无参数 GetEnumerator 方法以获得一个枚举器。由集合所定义的只能有一个这样的无参数 GetEnumerator 方法,但经常有多种枚举方式,以及通过参数控制枚举的方法。在这种情况下,集合可以使用迭代器实现返回可枚举接口之一的属性和方法。例如, Stack<t> 可能引入两个 IEnumerable<t> 类型的新属性, TopToBottom 和 BottomToTop 。
233
234using System.Collections.Generic;
235
236public class Stack<t>: IEnumerable<t>
237
238{
239
240T[] items;
241
242int count;
243
244public void Push(T data){…}
245
246public T Pop()P{…}
247
248public IEnumerator<t> GetEnumerator()
249
250{
251
252for(int i= count-1;i>=0;--i)
253
254{
255
256yield return items[i];
257
258}
259
260}
261
262public IEnumerable<t> TopBottom{
263
264get{
265
266return this;
267
268}
269
270}
271
272public IEnumerable<t> BottomToTop{
273
274get{
275
276for(int I = 0;i<count;i++) ()="" bottomtotop="" c#迭代器实现的枚举。下面的例子展示了,属性如何用于枚举堆栈元素。="" class="" get="" items[i];="" main="" return="" stack<int="" static="" system;="" test="" this="" toptobottom="" using="" void="" yield="" {="" }="" 属性的="" 属性返回一个使用="" 访问器只是返回="" ,因为堆栈自身是可枚举的。=""> stack = new Stack<int>();
277
278for(int i = 0 ;i<10 ;i++) stack.Push(i);
279
280for(int i in stack..TopToBottom) Console.Write(“{0}”,i);
281
282Console.WriteLine();
283
284for(int i in stack..BottomToTop) Console.Write(“{0}”,i);
285
286Console.WriteLine();
287
288}
289
290}
291
292当然,这些属性同样也可以在 foreach 语句之外使用。下面的例子将调用属性的结果传递给了一个单独的 Print 方法。该例子也展示了一个用作 FromToBy 接受参数的方法体的迭代器。
293
294using System;
295
296using System.Collections.Generic;
297
298class Test
299
300{
301
302static void Print(IEnumerable<int> collection)
303
304{
305
306foreach(int i in collection) Console.Write(“{0}”,i);
307
308Console.WriteLine();
309
310}
311
312static IEnumerable<int> FromToBy(int from ,int to , int by)
313
314{
315
316for(int i =from ;i<=to ;i +=by)
317
318{
319
320yield return I;
321
322}
323
324}
325
326
327
328
329static void Main ()
330
331{
332
333Stack<int> stack = new Stack<int>();
334
335for(int i= 0 ;i<10;i ++) stack.Push(i);
336
337Print(stack.TopToBottom);
338
339Print(stack.BottomToTop);
340
341Print(FromToBy(10,20,2));
342
343}
344
345}
346
347该例子的输出如下。
348
3499 8 7 6 5 4 3 2 1 0
350
3510 1 2 3 4 5 6 7 8 9
352
35310 12 14 16 18 20
354
355泛型和非泛型可枚举接口包含一个单一的成员,一个不接受参数的 GetEnumerator 方法 ,它一个枚举器接口。一个枚举充当一个枚举器工厂。每当调用了一个正确地实现了可枚举接口的类的 GetEnumerator 方法时,都会产生一个独立的枚举器。假定枚举的内部状态在两次调用 GetEnumerator 之间没有改变,返回的枚举器应该产生相同集合相同顺序的枚举值。在下面的例子中,这点应该保持,即使枚举的生存期发生交叠。
356
357using System;
358
359using System.Collections.Generic;
360
361class Test
362
363{
364
365static IEnumerable<int> FromTo(int from , int to)
366
367{
368
369while(from<=to) yield return from++;
370
371}
372
373static void Main ()
374
375{
376
377IEnumerable<int> e = FromTo(1,10);
378
379foreach(int x in e)
380
381{
382
383foreach(int y in e)
384
385<P class=2 style="MARGIN: 0cm 0cm 0pt 84pt; mso-para-</int></int></int></int></int></int></int></count;i++)></t></t></t></t></t></t></t></int></int></t></t></t></t></t></t></t></a.length;i++)></int></int></int></t></int></int></int></t></t></t></int></int></int></int></int></int></t>