C# 2.0 Specification(泛型五)

接泛型四

20.6.5语法歧义

在§20.9.3和§20.9.4中简单名字( simple-name )和成员访问( member-access )对于表达式来说容易引起语法歧义。例如,语句

F(G

  1<a,b>(7)); 
  2
  3  
  4
  5
  6可以被解释为对带有两个参数  G<a和b>(7)的F的调用  [1]  。同样,它还能被解释为对带有一个参数的F的调用,这是一个对带有两个类型实参和一个正式参数的泛型方法G的调用。 
  7
  8如果表达式可以被解析为两种不同的有效方法,那么在“&gt;”能被解析作为运算符的所有或一部分时,或者作为一个类型实参列表,那么紧随“&gt;”之后的标记将会被检查。如果它是如下之一: 
  9
 10{ } ] &gt; : ; , . ? 
 11
 12那么“&gt;”被解析作为类型实参列表。否则“&gt;”被解析作为一个运算符。 
 13
 14###  20.6.6对委托使用泛型方法 
 15
 16委托的实例可通过引用一个泛型方法的声明而创建。委托表达式确切的编译时处理,包括引用泛型方法的委托创建表达式,这在§20.9.6中进行了描述。 
 17
 18当通过委托调用一个泛型方法时,所使用的类型实参将在委托实例化时被确定。类型实参可以通过类型实参列表显式给定,或者通过类型推断(§20.6.4)而确定。如果采用类型推断,委托的参数类型将被用作推断处理过程的实参类型。委托的返回类型不用于推断。下面的例子展示了为一个委托实例化表达式提供类型实参的方法。 
 19
 20delegate int D(string s , int i) 
 21
 22delegate int E(); 
 23
 24class X 
 25
 26{ 
 27
 28public static T F<t>(string s ,T t){…} 
 29
 30public static T G<t>(){…} 
 31
 32static void Main() 
 33
 34{ 
 35
 36D d1 = new D(F<int>); //ok,  类型实参被显式给定 
 37
 38D d2 = new D(F); //ok,int  作为类型实参而被推断 
 39
 40E e1 = new E(G<int>); //ok,  类型实参被显式给定 
 41
 42E e2 = new E(G); //  错误,不能从返回类型推断 
 43
 44} 
 45
 46} 
 47
 48在先前的例子中,非泛型委托类型使用泛型方法实例化。你也可以使用泛型方法创建一个构造委托类型的实例。在所有情形下,当委托实例被创建时,类型实参被给定或可以被推断,但委托被调用时,可以不用提供类型实参列表(§15.3)。 
 49
 50**  
 51**
 52
 53###  20.6.7非泛型属性、事件、索引器或运算符 
 54
 55属性、事件、索引器和运算符他们自身可以没有类型实参(尽管他们可以出现在泛型类中,并且可从一个封闭类中使用类型实参)。如果需要一个类似属性泛型的构件,取而代之的是你必须使用一个泛型方法。 
 56
 57##  20.7  约束 
 58
 59泛型类型和方法声明可以可选的指定类型参数约束,这通过在声明中包含类型参数约束语句就可以做到。 
 60
 61_type-parameter-constraints-clauses_ (类型参数约束语句:) 
 62
 63_type-parameter-constraints-clause_ (类型参数约束语句) 
 64
 65_type-parameter-constraints-clauses type-parameter-constraints-clause_ (类型参数约束语句 类型参数约束语句) 
 66
 67_type-parameter-constraints-clause:_ (类型参数约束语句:) 
 68
 69where  _type-parameter : type-parameter-constraints_ (where 类型参数:类型参数约束) 
 70
 71_type-parameter-constraints:_ (类型参数约束:) 
 72
 73_class-constraint_ (类约束) 
 74
 75_interface-constraints_ (接口约束) 
 76
 77_constructor-constraint_ (构造函数约束) 
 78
 79_class-constraint , interface-constraints_ (类约束,接口约束) 
 80
 81_class-constraint , constructor-constraint_ (类约束,构造函数约束) 
 82
 83_interface-constraints , constructor-constraint_ (接口约束,构造函数约束) 
 84
 85_class-constraint , interface-constraints , constructor-constraint_ (类约束,接口约束,构造函数约束) 
 86
 87_class-constraint:_ (类约束:) 
 88
 89_class-type_ (类类型) 
 90
 91_interface-constraint:_ (接口约束:) 
 92
 93_interface-constraint_ (接口约束) 
 94
 95_interface-constraints , interface-constraints_ (接口约束,接口约束) 
 96
 97_interface-constraints:_ (接口约束:) 
 98
 99_interface-type_ (接口类型) 
100
101_constructor-constraint:_ (构造函数约束:) 
102
103new  ( ) 
104
105每个类型参数约束语句由标志  where  紧接着类型参数的名字,紧接着冒号和类型参数的约束列表。对每个类型参数只能有一个  where  语句,但  where  语句可以以任何顺序列出。与属性访问器中的  get  和  set  标志相似,  where  语句不是关键字。 
106
107  
108
109
110在  where  语句中给定的约束列表可以以这个顺序包含下列组件:一个单一的类约束、一个或多个接口约和构造函数约束  new  ()。 
111
112如果约束是一个类类型或者接口类型,这个类型指定类型参数必须支持的每个类型实参的最小“基类型”。无论什么时候使用一个构造类型或者泛型方法,在编译时对于类型实参的约束建会被检查。所提供的类型实参必须派生于或者实现那个类型参数个定的所有约束。 
113
114被指定作为类约束的类型必须遵循下面的规则。 
115
116  * 该类型必须是一个类类型。 
117  * 该类型必须是密封的(  sealed  )。 
118  * 该类型不能是如下的类型:  System.Array  ,  System.Delegate  ,  System.Enum  ,或者  System.ValueType  类型。 
119  * 该类型不能是  object  。由于所有类型派生于  object  ,如果容许的话这种约束将不会有什么作用。 
120  * 至多,对于给定类型参数的约束可以是一个类类型。 
121
122
123
124作为接口约束而被指定的类型必须满足如下的规则。 
125
126  * 该类型必须是一个接口类型。 
127  * 在一个给定的  where  语句中相同的类型不能被指定多次。 
128
129
130
131在很多情况下,约束可以包含任何关联类型的类型参数或者方法声明作为构造类型的一部分,并且可以包括被声明的类型,但约束不能是一个单一的类型参数。 
132
133被指定作为类型参数约束的任何类或者接口类型,作为泛型类型或者被声明的方法,必须至少是可访问的(§10.5.4)。 
134
135如果一个类型参数的where 语句包括new()形式的构造函数约束,则使用new 运算符创建该类型(§20.8.2)的实例是可能的。用于带有一个构造函数约束的类型参数的任何类型实参必须有一个无参的构造函数(详细情形参看§20.7)。 
136
137下面是可能约束的例子 
138
139interface IPrintable 
140
141{ 
142
143void Print(); 
144
145} 
146
147  
148
149
150interface IComparable<t>
151
152{ 
153
154int CompareTo(T value); 
155
156} 
157
158interface IKeyProvider<t>
159
160{ 
161
162T GetKey(); 
163
164} 
165
166class Printer<t> where T:IPrintable{…} 
167
168class SortedList<t> where T: IComparable<t>{…} 
169
170class Dictionary<k,v>
171
172where K:IComparable<k>
173
174where: V: IPrintable,IKeyProvider<k>,new() 
175
176{ 
177
178179
180} 
181
182下面的例子是一个错误,因为它试图直接使用一个类型参数作为约束。 
183
184class Extend<t ,="" u=""> where U:T{…}//  错误 
185
186约束的类型参数类型的值可以被用于访问约束暗示的实例成员。在例子 
187
188interface IPrintable 
189
190{ 
191
192void Print(); 
193
194} 
195
196class Printer<t> where T:IPrintable 
197
198{ 
199
200void PrintOne(T x) 
201
202{ 
203
204x.Pint(); 
205
206} 
207
208} 
209
210IPrintable  的方法可以在x上被直接调用,因为T被约束总是实现  IPrintable  。 
211
212###  20.7.1遵循约束 
213
214无论什么时候使用构造类型或者引用泛型方法,所提供的类型实参都将针对声明在泛型类型,或者方法中的类型参数约束作出检查。对于每个  where  语句,对应于命名的类型参数的类型实参A将按如下每个约束作出检查。 
215
216  
217
218
219  * 如果约束是一个类类型或者接口类型,让C表示提供类型实参的约束,该类型实参将替代出现在约束中的任何类型参数。为了遵循约束,类型A必须按如下方式可别转化为类型C: 
220
221
222
223\-  同一转换(§6.1.1) 
224
225\-  隐式引用转换(§6.1.4) 
226
227\-  装箱转换(§6.1.5) 
228
229\-  从类型参数A到C(§20.7.4)的隐式转换。 
230
231  * 如果约束是new(),类型实参A不能是abstract并,且必须有一个公有的无参的构造函数。如果如下之一是真实的这将可以得到满足。 
232
233
234
235\-  A是一个值类型(如§4.1.2中所描述的,所有值类型都有一个公有默认构造函数)。 
236
237\-  A是一个非abstract类,并且 A包含一个无参公有构造函数。 
238
239\-  A是一个非abstract类,并且有一个默认构造函数(§10.10.4)。 
240
241如果一个或多个类型参数的约束通过给定的类型实参不能满足,将会出现编译时错误。 
242
243因为类型参数不被继承,同样约束也决不被继承。在下面的例子中,D 必须在其类型参数T上指定约束,以满足由基类B<t>所施加的约束。相反,类E不需要指定约束,因为对于任何T,List<t>实现了IEnumerable接口。 
244
245class B<t> where T: IEnumerable{…} 
246
247class D<t>:B<t> where T:IEnumerable{…} 
248
249class E<t>:B<list<t>&gt;{…} 
250
251###  20.7.2 在类型参数上的成员查找 
252
253在由类型参数T给定的类型中,成员查找的结果取决于为T所指定的约束(如果有的话)。如果T没有约束或者只有  new  ()约束,在T上的成员查找,像在  object  上的成员查找一样,返回一组相同的成员。否则,成员查找的第一个阶段,将考虑T所约束的每个类型的所有成员,结果将会被合并,然后隐藏成员将会从合并结果中删除。 
254
255  
256
257
258在泛型出现之前,成员查找总是返回在类中唯一声明的一组成员,或者一组在接口中唯一声明的成员, 也可能是  object  类型。在类型参数上的成员查找做出了一些改变。当一个类型参数有一个类约束和一个或多个接口约束时,成员查找可以返回一组成员,这些成员有一些是在类中声明的,还有一些是在接口中声明的。下面的附加规则处理了这种情况。 
259
260  * 在成员查找过程(§20.9.2)中,在除了object之外的类中声明的成员隐藏了在接口中声明的成员。 
261  * 在方法和索引器的重载决策过程中,如果任何可用成员在一个不同于object的类中声明,那么在接口中声明的所有成员都将从被考虑的成员集合中删除。 
262
263
264
265这些规则只有在将一个类约束和接口约束绑定到类型参数上时才有效。通俗的说法是,在一个类约束中定义的成员,对于在接口约束的成员来说总是首选。 
266
267###  20.7.3 类型参数和装箱 
268
269当一个结构类型重写继承于  System.Object(Equals , GetHashCode  或  ToString)  的虚拟方法,通过结构类型的实例调用虚拟方法将不会导致装箱。即使当结构被用作一个类型参数,并且调用通过类型参数类型的实例而发生,情况也是如此。例如 
270
271using System; 
272
273struct Counter 
274
275{ 
276
277int value; 
278
279public override string ToString() 
280
281{ 
282
283value++; 
284
285return value.ToString(); 
286
287} 
288
289} 
290
291class Program 
292
293{ 
294
295static void Test<t>() where T:new() 
296
297{ 
298
299T x = new T(); 
300
301Console.WriteLine(x.ToString()); 
302
303Console.WriteLine(x.ToString()); 
304
305Console.WriteLine(x.ToString()); 
306
307} 
308
309static void Main() 
310
311{ 
312
313Test<counter>(); 
314
315} 
316
317} 
318
319  
320
321
322程序的输出如下 
323
3241 
325
3262 
327
3283 
329
330尽管推荐不要让  ToString  带有附加效果(  side effect  )  [2]  ,但这个例子说明了对于三次  x.ToString()  的调用不会发生装箱。 
331
332当在一个约束的类型参数上访问一个成员时,装箱决不会隐式地发生。例如,假定一个接口  ICounter  包含了一个方法  Increment  ,它可以被用来修改一个值。如果  ICounter  被用作一个约束,  Increment  方法的实现将通过  Increment  在其上调用的变量的引用而被调用,这个变量不是一个装箱拷贝。 
333
334using System; 
335
336interface ICounter 
337
338{ 
339
340void Increment(); 
341
342} 
343
344struct Counter:ICounter 
345
346{ 
347
348int value; 
349
350public override string ToString() 
351
352{ 
353
354return value.ToString(); 
355
356} 
357
358void ICounter.Increment(){ 
359
360value++; 
361
362} 
363
364} 
365
366class Program 
367
368{ 
369
370static void Test<t>() where T:new ,ICounter{ 
371
372T x = new T(); 
373
374Console.WriteLine(x); 
375
376x.Increment(); //  修改  x` 
377
378Console.WriteLine(x); 
379
380((ICounter)x).Increment(); //  修改  x  的装箱拷贝 
381
382Console.WriteLine(x); 
383
384} 
385
386static void Main() 
387
388{ 
389
390Test<counter>(); 
391
392} 
393
394} 
395
396  
397
398
399对变量x的首次调用  Increment  修改了它的值。这与第二次调用  Increment  是不等价的,第二次修改的是x装箱后的拷贝,因此程序的输出如下 
400
401###  20.7.4包含类型参数的转换 
402
403在类型参数T上允许的转换,取决于为T所指定的约束。所有约束的或非约束的类型参数,都可以有如下转换。 
404
405  * 从T到T的隐式同一转换。 
406  * 从T到  object  的隐式转换。在运行时,如果T是一个值类型,这将通过一个装箱转换进行。否则,它将作为一个隐式地引用转换。 
407  * 从  object  到T的隐式转换。在运行时,如果T是一个值类型,这将通过一个取消装箱操作而进行。否则它将作为一个显式地引用转换。 
408  * 从T到任何接口类型的显式转换。在运行时,如果T是一个值类型,这将通过一个装箱转换而进行。否则,它将通过一个显式地引用转换而进行。 
409  * 从任何接口类型到T的隐式转换。在运行时,如果T是一个值类型,这将通过一个取消装箱操作而进行。否则,它将作为一个显式引用转换而进行。 
410
411
412
413如果类型参数T指定一个接口I作为约束,将存在下面的附加转换。 
414
415  * 从T到I的隐式转换,以及从T到I的任何基接口类型的转换。在运行时,如果T是一个值类型,这将作为一个装箱转换而进行。否则,它将作为一个隐式地引用转换而进行。 
416
417
418
419如果类型参数T指定类型C作为约束,将存在下面的附加转换: 
420
421  * 从T到C的隐式引用转换,从T到任何C从中派生的类,以及从T到任何从其实现的接口。 
422  * 从C到T的显式引用转换,从C从中派生的类  [3]  到T,以及C实现的任何接口到T 
423
424
425
426  
427
428
429  * 如果存在从C 到A的隐式用户定义转换,从T到 A的隐式用户定义转换。 
430  * 如果存在从A 到C的显式用户定义转换,从A到 T的显式用户定义转换。 
431  * 从null类型到T的隐式引用转换 
432
433
434
435一个带有元素类型的数组类型T具有  object  和  System.Array  之间的相互转换(§6.1.4,§6.2.3)。如果T有作为约束而指定的类类型,将有如下附加规则 
436
437  * 从带有元素类型T的数组类型A  T  到带有元素类型U的数组类型A  U  的隐式引用转换,并且如果下列二者成立的话,将存在从A  U  到A  T  显式引用转换: 
438
439
440
441\-  A  T  和A  U  有相同数量的维数。 
442
443\-  U是这些之一:C,C从中派生的类型,C所实现的接口,作为在T上的约束而指定的接口I,或I的基接口。 
444
445先前的规则不允许从非约束类型参数到非接口类型的直接隐式转换,这可能有点奇怪。其原因是为了防止混淆,并且使得这种转换的语义更明确。例如,考虑下面的声明。 
446
447class X<t>
448
449{ 
450
451public static long F(T t){ 
452
453return (long)t; // ok,  允许转换 
454
455} 
456
457} 
458
459如果t到  int  的直接显式转换是允许的,你可能很容易以为  X<int>.F(7)  将返回7L。但实际不是,因为标准的数值转换只有在类型在编译时是已知的时候才被考虑。为了使语义更清楚,先前的例子必须按如下形式编写。 
460
461class X<t>
462
463{ 
464
465public static long F(T t) 
466
467{ 
468
469return (long)(object)t; //ok;  允许转换 
470
471} 
472
473} 
474
475  
476
477
478* * *
479
480[1]  这种情况下“&gt;”被解释为大于运算符。 
481
482[2]  在程序中重写ToString时,一般不推荐添加这种类似的计算逻辑,因为它的这种结果变化不易控制,增加了调试程序的复杂性。 
483
484[3]  C的基类或其基类的基类等。</t></int></t></counter></t></counter></t></list<t></t></t></t></t></t></t></t></t></k></k></k,v></t></t></t></t></t></int></int></t></t></a和b></a,b>
Published At
Categories with Web编程
Tagged with
comments powered by Disqus