接泛型四
20.6.5語法歧義
在§20.9.3和§20.9.4中簡單名字(simple-name)和成員存取(member-access)對於表達式來說容易引起語法歧義。例如,語句
F(G<A,B>(7));
{ } ] > : ; , . ?
那麼「>」被解析作為類型實參列表。否則“>”被解析作為一個運算符。
20.6.6對委託使用泛型方法
委託的實例可透過引用一個泛型方法的聲明而建立。委託表達式確切的編譯時處理,包括引用泛型方法的委託創建表達式,這在§20.9.6中進行了描述。
當透過委託呼叫一個泛型方法時,所使用的類型實參將在委託實例化時被確定。類型實參可以透過類型實參清單明確給定,或透過類型推論(§20.6.4)而確定。如果採用類型推斷,委託的參數類型將被用作推斷處理過程的實參類型。委託的回傳類型不用於推斷。下面的範例展示了為一個委託實例化表達式提供類型實參的方法。
delegate int D(string s , int i) delegate int E(); class X { public static T F<T>(string s ,T t){…} public static T G<T>(){…} static void Main() { D d1 = new D(F<int>); //ok,类型实参被显式给定 D d2 = new D(F); //ok,int作为类型实参而被推断 E e1 = new E(G<int>); //ok,类型实参被显式给定 E e2 = new E(G); //错误,不能从返回类型推断 } }
在先前的例子中,非泛型委託類型使用泛型方法實例化。你也可以使用泛型方法來建立一個建構委託類型的實例。在所有情況下,當委託實例被創建時,類型實參被給定或可以被推斷,但委託被調用時,可以不用提供類型實參列表(§15.3)。
20.6.7非泛型屬性、事件、索引器或運算子
屬性、事件、索引器和運算子他們本身可以沒有類型實參(儘管他們可以出現在泛型類別中,並且可從一個封閉類別中使用型別實參)。如果需要一個類似屬性泛型的構件,取而代之的是你必須使用一個泛型方法。
20.7約束
泛型類型和方法宣告可以可選的指定型別參數約束,這透過在宣告中包含型別參數約束語句就可以做到。
type-parameter-constraints-clauses(类型参数约束语句:) type-parameter-constraints-clause(类型参数约束语句) type-parameter-constraints-clauses type-parameter-constraints-clause(类型参数约束语句 类型参数约束语句) type-parameter-constraints-clause:(类型参数约束语句:) where type-parameter : type-parameter-constraints(where 类型参数:类型参数约束) type-parameter-constraints:(类型参数约束:) class-constraint(类约束) interface-constraints(接口约束) constructor-constraint(构造函数约束) class-constraint , interface-constraints(类约束,接口约束) class-constraint , constructor-constraint(类约束,构造函数约束) interface-constraints , constructor-constraint(接口约束,构造函数约束) class-constraint , interface-constraints , constructor-constraint(类约束,接口约束,构造函数约束) class-constraint:(类约束:) class-type(类类型) interface-constraint:(接口约束:) interface-constraint(接口约束) interface-constraints , interface-constraints(接口约束,接口约束) interface-constraints:(接口约束:) interface-type(接口类型) constructor-constraint:(构造函数约束:) new ( )
每个类型参数约束语句由标志where 紧接着类型参数的名字,紧接着冒号和类型参数的约束列表。对每个类型参数只能有一个where 语句,但where语句可以以任何顺序列出。与属性访问器中的get和set标志相似,where 语句不是关键字。
在where语句中给定的约束列表可以以这个顺序包含下列组件:一个单一的类约束、一个或多个接口约和构造函数约束new ()。
如果约束是一个类类型或者接口类型,这个类型指定类型参数必须支持的每个类型实参的最小“基类型”。无论什么时候使用一个构造类型或者泛型方法,在编译时对于类型实参的约束建会被检查。所提供的类型实参必须派生于或者实现那个类型参数个定的所有约束。
被指定作为类约束的类型必须遵循下面的规则。
该类型必须是一个类类型。
该类型必须是密封的(sealed)。
该类型不能是如下的类型:System.Array,System.Delegate,System.Enum,或者System.ValueType类型。
该类型不能是object。由于所有类型派生于object,如果容许的话这种约束将不会有什么作用。
至多,对于给定类型参数的约束可以是一个类类型。
作为接口约束而被指定的类型必须满足如下的规则。
该类型必须是一个接口类型。
在一个给定的where语句中相同的类型不能被指定多次。
在很多情况下,约束可以包含任何关联类型的类型参数或者方法声明作为构造类型的一部分,并且可以包括被声明的类型,但约束不能是一个单一的类型参数。
被指定作为类型参数约束的任何类或者接口类型,作为泛型类型或者被声明的方法,必须至少是可访问的(§10.5.4)。
如果一个类型参数的where 语句包括new()形式的构造函数约束,则使用new 运算符创建该类型(§20.8.2)的实例是可能的。用于带有一个构造函数约束的类型参数的任何类型实参必须有一个无参的构造函数(详细情形参看§20.7)。
下面是可能约束的例子
interface IPrintable { void Print(); } interface IComparable<T> { int CompareTo(T value); } interface IKeyProvider<T> { T GetKey(); } class Printer<T> where T:IPrintable{…} class SortedList<T> where T: IComparable<T>{…} class Dictionary<K,V> where K:IComparable<K> where: V: IPrintable,IKeyProvider<K>,new() { … }
下面的例子是一个错误,因为它试图直接使用一个类型参数作为约束。
class Extend<T , U> where U:T{…}//错误
约束的类型参数类型的值可以被用于访问约束暗示的实例成员。在例子
interface IPrintable { void Print(); } class Printer<T> where T:IPrintable { void PrintOne(T x) { x.Pint(); } }
IPrintable的方法可以在x上被直接调用,因为T被约束总是实现IPrintable。
20.7.1遵循约束
无论什么时候使用构造类型或者引用泛型方法,所提供的类型实参都将针对声明在泛型类型,或者方法中的类型参数约束作出检查。对于每个where 语句,对应于命名的类型参数的类型实参A将按如下每个约束作出检查。
如果约束是一个类类型或者接口类型,让C表示提供类型实参的约束,该类型实参将替代出现在约束中的任何类型参数。为了遵循约束,类型A必须按如下方式可别转化为类型C:
- 同一转换(§6.1.1)
- 隐式引用转换(§6.1.4)
- 装箱转换(§6.1.5)
- 从类型参数A到C(§20.7.4)的隐式转换。
如果约束是new(),类型实参A不能是abstract并,且必须有一个公有的无参的构造函数。如果如下之一是真实的这将可以得到满足。
- A是一个值类型(如§4.1.2中所描述的,所有值类型都有一个公有默认构造函数)。
- A是一个非abstract类,并且 A包含一个无参公有构造函数。
- A是一个非abstract类,并且有一个默认构造函数(§10.10.4)。
如果一个或多个类型参数的约束通过给定的类型实参不能满足,将会出现编译时错误。
因为类型参数不被继承,同样约束也决不被继承。在下面的例子中,D 必须在其类型参数T上指定约束,以满足由基类B8742468051c85b06f0a0af9e3e506b5c所施加的约束。相反,类E不需要指定约束,因为对于任何T,
List<T>实现了IEnumerable接口。 class B<T> where T: IEnumerable{…} class D<T>:B<T> where T:IEnumerable{…} class E<T>:B<List<T>>{…}
20.7.2 在类型参数上的成员查找
在由类型参数T给定的类型中,成员查找的结果取决于为T所指定的约束(如果有的话)。如果T没有约束或者只有new ()约束,在T上的成员查找,像在object上的成员查找一样,返回一组相同的成员。否则,成员查找的第一个阶段,将考虑T所约束的每个类型的所有成员,结果将会被合并,然后隐藏成员将会从合并结果中删除。
在泛型出现之前,成员查找总是返回在类中唯一声明的一组成员,或者一组在接口中唯一声明的成员, 也可能是object类型。在类型参数上的成员查找做出了一些改变。当一个类型参数有一个类约束和一个或多个接口约束时,成员查找可以返回一组成员,这些成员有一些是在类中声明的,还有一些是在接口中声明的。下面的附加规则处理了这种情况。
在成员查找过程(§20.9.2)中,在除了object之外的类中声明的成员隐藏了在接口中声明的成员。
在方法和索引器的重载决策过程中,如果任何可用成员在一个不同于object的类中声明,那么在接口中声明的所有成员都将从被考虑的成员集合中删除。
这些规则只有在将一个类约束和接口约束绑定到类型参数上时才有效。通俗的说法是,在一个类约束中定义的成员,对于在接口约束的成员来说总是首选。
20.7.3 类型参数和装箱
当一个结构类型重写继承于System.Object(Equals , GetHashCode或ToString)的虚拟方法,通过结构类型的实例调用虚拟方法将不会导致装箱。即使当结构被用作一个类型参数,并且调用通过类型参数类型的实例而发生,情况也是如此。例如
using System; struct Counter { int value; public override string ToString() { value++; return value.ToString(); } } class Program { static void Test<T>() where T:new() { T x = new T(); Console.WriteLine(x.ToString()); Console.WriteLine(x.ToString()); Console.WriteLine(x.ToString()); } static void Main() { Test<Counter>(); } }
程序的输出如下
1 2 3
尽管推荐不要让ToString带有附加效果(side effect)[2],但这个例子说明了对于三次x.ToString()的调用不会发生装箱。
当在一个约束的类型参数上访问一个成员时,装箱决不会隐式地发生。例如,假定一个接口ICounter包含了一个方法Increment,它可以被用来修改一个值。如果ICounter被用作一个约束,Increment方法的实现将通过Increment在其上调用的变量的引用而被调用,这个变量不是一个装箱拷贝。
using System; interface ICounter { void Increment(); } struct Counter:ICounter { int value; public override string ToString() { return value.ToString(); } void ICounter.Increment(){ value++; } } class Program { static void Test<T>() where T:new ,ICounter{ T x = new T(); Console.WriteLine(x); x.Increment(); //修改x` Console.WriteLine(x); ((ICounter)x).Increment(); //修改x的装箱拷贝 Console.WriteLine(x); } static void Main() { Test<Counter>(); } }
对变量x的首次调用Increment修改了它的值。这与第二次调用Increment是不等价的,第二次修改的是x装箱后的拷贝,因此程序的输出如下
20.7.4包含类型参数的转换
在类型参数T上允许的转换,取决于为T所指定的约束。所有约束的或非约束的类型参数,都可以有如下转换。
从T到T的隐式同一转换。
从T到object 的隐式转换。在运行时,如果T是一个值类型,这将通过一个装箱转换进行。否则,它将作为一个隐式地引用转换。
从object到T的隐式转换。在运行时,如果T是一个值类型,这将通过一个取消装箱操作而进行。否则它将作为一个显式地引用转换。
从T到任何接口类型的显式转换。在运行时,如果T是一个值类型,这将通过一个装箱转换而进行。否则,它将通过一个显式地引用转换而进行。
从任何接口类型到T的隐式转换。在运行时,如果T是一个值类型,这将通过一个取消装箱操作而进行。否则,它将作为一个显式引用转换而进行。
如果类型参数T指定一个接口I作为约束,将存在下面的附加转换。
从T到I的隐式转换,以及从T到I的任何基接口类型的转换。在运行时,如果T是一个值类型,这将作为一个装箱转换而进行。否则,它将作为一个隐式地引用转换而进行。
如果类型参数T指定类型C作为约束,将存在下面的附加转换:
从T到C的隐式引用转换,从T到任何C从中派生的类,以及从T到任何从其实现的接口。
从C到T的显式引用转换,从C从中派生的类[3]到T,以及C实现的任何接口到T
如果存在从C 到A的隐式用户定义转换,从T到 A的隐式用户定义转换。
如果存在从A 到C的显式用户定义转换,从A到 T的显式用户定义转换。
从null类型到T的隐式引用转换
一个带有元素类型的数组类型T具有object和System.Array之间的相互转换(§6.1.4,§6.2.3)。如果T有作为约束而指定的类类型,将有如下附加规则
从带有元素类型T的数组类型AT到带有元素类型U的数组类型AU的隐式引用转换,并且如果下列二者成立的话,将存在从AU到AT显式引用转换:
- AT和AU 有相同数量的维数。
- U是这些之一:C,C从中派生的类型,C所实现的接口,作为在T上的约束而指定的接口I,或I的基接口。
先前的规则不允许从非约束类型参数到非接口类型的直接隐式转换,这可能有点奇怪。其原因是为了防止混淆,并且使得这种转换的语义更明确。例如,考虑下面的声明。
class X<T> { public static long F(T t){ return (long)t; // ok,允许转换 }
}
如果t到int的直接显式转换是允许的,你可能很容易以为Xbd43222e33876353aff11e13a7dc75f6.F(7)将返回7L。但实际不是,因为标准的数值转换只有在类型在编译时是已知的时候才被考虑。为了使语义更清楚,先前的例子必须按如下形式编写。
class X<T> { public static long F(T t) { return (long)(object)t; //ok;允许转换 } }
[1] 这种情况下“>”被解释为大于运算符。
[2] 在程序中重写ToString时,一般不推荐添加这种类似的计算逻辑,因为它的这种结果变化不易控制,增加了调试程序的复杂性。
[3] C的基类或其基类的基类等。
以上就是C# 2.0 Specification(泛型五)的内容,更多相关内容请关注PHP中文网(www.php.cn)!