C# 2.0 Specification (泛型三)
2024-07-21 02:20:02
供稿:网友
菜鸟学堂:
接泛型二
这篇文章是翻译的微软的技术文章.供学习c#的朋友参考,请勿用于商业目的。http://msdn.microsoft.com/vcsharp/team/language/default.aspx
20.4 泛型委托声明
委托声明可以包含类型参数。
delegate-declaration:
attributes opt delegate-modifiers op t delegate return-type identifier type-parameter-list opt
(formal-parameter-list opt) type-parameter-constraints-clauses opt;
(委托声明: 特性可选 委托修饰符可选 delegate 返回类型 标识符 类型参数列表可选 (正式参数列表可选 )类型参数约束语句可选
使用类型参数声明的委托是一个泛型委托声明。委托声明只有在支持类型参数列表时,才能支持类型参数约束语句(§20.7)。除了所指出的之外,泛型委托声明和常规的委托声明遵循相同的规则。泛型委托声明中的每个类型参数,在与委托关联的特定声明空间(§3.3)定义了一个名字。在委托声明中的类型参数的作用域包括返回类型、正式参数列表和类型参数约束语句。
像其他泛型类型声明一样,必须给定类型实参以形成构造委托类型。构造委托类型的参数和返回值,由委托声明中构造委托类型的每个类型参数对应的实参替代所形成。而结果返回类型和参数类型用于确定什么方法与构造委托类型兼容。例如
delegate bool predicate<t>(t value)
class x
{
static bool f(int i){…}
static bool g(string s){…}
static void main(){
predicate<int> p1 = f;
predicate<string> p2=g;
}
}
注意在先前的main方法中的两个赋值等价于下面的较长形式.
static void main(){
predicate<int> p1 = new predicate<int>(f);
predicate<string> p2 = new predicate<string>(g);
}
由于方法组转换,较短的形式也是可以的,这在§21.9中有说明。
20.5构造类型
泛型类型声明自身并不表示一个类型。相反,泛型类型声明通过应用类型实参的方式被用作形成许多不同类型的“蓝图”。类型参数被写在尖括号之间,并紧随泛型类型声明名字之后。使用至少一个实参而被命名的类型被称为构造类型(constructed type)。构造类型可以用在语言中类型名字可以出现的大多数地方。
type-name:(类型名字:)
namespace-or-type-name(命名空间或类型名字)
namespace-or-type-name:(命名空间或类型名字:)
identifier type-argument-list(标识符类型实参列表可选)
namespace-or-type-name. identifier(命名空间或类型名字.标识符)
type-argument-list opt(类型实参列表可选)
构造类型也能被用在表达式中作为简单名字(§20.9.3)或者访问一个成员(§20.9.4)。
当一个命名空间或类型名字被计算时,只有带有正确数量类型参数的泛型类型会被考虑。由此,只要类型有不同数量的类型参数并且声明在不同的命名空间,那么使用相同的标识符标识不同的类型是可能的。这对于在同一程序中混合使用泛型和非泛型类是很有用的。
namespace system.collections
{
class queue{…}
}
namespace sysetm.collections.generic
{
class queue<elementtype>{…}
}
namespace myapplication
{
using system.collections;
using system.collections.generic;
class x
{
queue q1; //system.collections.queue
queue<int> q2;//system.collections.generic.queue
}
}
在这些代码中对于名字查找的详细规则在§20.9中进行了描述。在这些代码中对于模糊性的决议在§20.6.5中进行了描述。
类型名字可能标识一个构造类型,尽管它没有直接指定类型参数。这种情况在一个类型嵌套在一个泛型类声明中时就会出现,并且包含声明的实例类型将因为名字查找(§20.1.2)而被隐式地使用。
class outer<t>
{
public class inner{…}
public inner i; //i的类型是outer<t>.inner
}
在不安全代码中,构造类型不能被用作非托管类型(§18.2)。
20.5.1类型实参
在一个类型参数列表中的每个实参都只是一个类型。
type-argument-list:(类型实参列表:)
<type-arguments>(<类型实参>)
type-arguments:(类型实参:)
type-argument(类型实参)
type-arguments, type-argument(类型实参,类型实参)
type-argument:(类型实参:)
type(类型)
类型实参反过来也可以是构造类型或类型参数。在不安全代码中(§18),类型实参不能是指针类型。每个类型实参必须遵循对应类型参数(§20.7.1)上的任何约束。
20.5.2开放和封闭类型
所有类型都可以被分为开放类型(open type)或封闭类型(closed type)。开放类型是包含类型参数的类型。更明确的说法是
类型参数定义了一个开放类型
数组类型只有当其元素是一个开放类型时才是开放类型
构造类型只有当其类型实参中的一个或多个是开放类型时,它才是开放类型
非开放类型都是封闭类型。
在运行时,在泛型类型声明中的所有代码都在一个封闭构造类型的上下文执行,这个封闭构造类型是通过将类型实参应用到泛型声明中创建的。在泛型类型中的每个类型实参被绑定到一个特定运行时类型。所有语句和表达式的运行时处理总是针对封闭类型发生,而开放类型只发生在编译时处理。
每个封闭构造类型都有它自己的一组静态变量,它们并不被其他封闭类型共享。因为在运行时不存在开放类型,所以开放类型没有关联的静态变量。如果两个封闭构造类型是从同一个类型声明构造的,并且对应的类型实参也是相同的类型,那么它们就是相同的类型。
20.5.3构造类型的基类和接口
构造类类型有一个直接基类,就像是一个简单类类型。如果泛型类声明没有指定基类,其基类为object。如果基类在泛型类声明中被指定,构造类型的基类通过将在基类声明中的每个类型参数,替代为构造类型对应类型实参而得到。给定泛型类声明
class b<u , v>{…}
class g<t>:b<string , t[]>{…}
构造类型g<int>的基类将会是b<string , int[]>。
相似地,构造类、结构和接口类型有一组显式的基接口。显式基接口通过接受泛型类型声明中的显式基接口声明和某种替代而形成,这种替代是将在基接口声明中的每个类型参数,替代为构造类型的对应类型实参。
一个类型的所有基类和基接口通过递归地得到中间基类和接口的基类与接口而形成。例如,给定泛型类声明
class a {…}
class b<t>:a{…}
class c<t>:b<icomparable<t>>{…}
class d<t>:c<t[]>{…}
d<int>的基类是c<int[]>,b<icomparable<int[]>>,a和object。
20.5.4构造类型的成员
构造类型的非继承成员通过替代成员声明的类型实参,构造类型的对应类型实参而得到。
例如,给定泛型类声明
class gen<t,u>
{
public t[,],a;
public void g(int i ,t t , gen<u, t> gt){…}
public u prop(get{…}) set{…}}
public int h{double d}{…}
}
构造类型gen<int[],icomparable<string>>有如下的成员。
public int[,][] a;
public void g(int i , int[] t , gen<icomparable<string>,int[] gt>){…}
public icomparable<string> prop{get{…} set{…}}
public int h(double d){…}
注意替代处理是基于类型声明的语义意义的,并不是简单的基于文本的替代。在泛型类声明gen中的成员a的类型是“t的二维数组” 因此在先前实例化类型中的成员a的类型是“int型的一维数组的二维数组”或int[,][]。
构造类型的继承成员以一种相似的方法得到。首先直接基类的所有成员是已经确定的。如果基类自身是构造类型这可能包括当前规则的递归应用。然后,继承成员的每一个通过将成员声明中的每个类型参数,替代为构造类型对应类型实参而被转换。
class b<u>
{
public u f(long index){…}
}
class d<t>:b<t[]>
{
public t g(string s){…}
}
在先前的例子中,构造类型d<int>的非继承成员public int g(string s)通过替代类型参数t的类型实参int而得到。d<int>也有一个从类声明b而来的继承成员。这个继承成员通过首先确定构造类型b<t[]>的成员而被确定,b<t[]>成员的确定是通过将u替换为替换为t[],产生public t[] f(long index)。然后类型实参int替换了类型参数t,产生继承成员public int[] f(long index)。
20.5.5构造类型的可访问性
当构造类型c<t1,…,tn>的所有部分c,t1,…,tn 可访问时,那么它就是可访问的。例如,如果泛型类型名c是public,并且所有类型参数t1,…,tn也是public ,那么构造类型的可访问性也是public 。如果类型名或类型实参之一是private,那么构造类型的可访问性是private。如果类型实参之一可访问性是protected,另一个是internal,那么构造类型的可访问性仅限于该类,以及本程序集之内的子类。
20.5.6转换
构造类型遵循与非泛型类型相同的规则(§6)。当应用这些规则时,构造类型的基类和接口必须按§20.5.3中所描述的方式确定。
除了那些在§6中所描述的之外,构造引用类型之间不存在特别的转换。尤其是,不像数组类型,构造引用类型不允许“co-variant”转换。也就是说,类型list<b>不能转换到类型list<a>(无论是隐式或显式)即使是b派生于a也是如此。同样,也不存在从list<b>到list<object>的转换。
对于这一点的基本原理是很简单的:如果可以转换到list<a>,很显然你可以存储一个类型a的值到这个list中。这将破坏在list<b>类型中的每个对象总是类型b的值这种不变性,或者当在集合类上赋值时,将出现不可预料的错误。
转换的行为和运行时类型检查演示如下。
class a {…}
class b:a{…}
class colletion{…}
class list<t>:collection{…}
class test
{
void f()
{
list<a> lista = new list<a>();
list<b> listb= new list<b>();
collection c1 = lista; //ok,list<a>是一个集合
collection c2 = listb; //ok,list<b>是一个集合
list<a> a1 = listb; //错误,没有隐式的转换
list<a> a2 = (list<a>)listb; //错误,没有显式的转换
}
}
20.5.7system.nullable<t>类型
在.net基类库中定义了泛型结构类型system.nullable<t>泛型结构类型,它表示一个类型t的值可以为null。system.nullable<t>类型在很多情形下是很有用的,例如用于指示数据库表的可空列,或者xml元素中的可选特性。
可以从一个null类型向任何由system.nullable<t>类型构造的类型作隐式地转换。这种转换的结果就是system.nullable<t>的默认值。也就是说,可以这样写
nullable<int> x = null;
nullable<string> y = null;
和下面的写法相同。
nullable<int> x = nullable<int>.default;
nullable<string> y = nullable<string>.default;
20.5.8使用别名指令
使用别名可以命名一个封闭构造类型,但不能命名一个没有提供类型实参的泛型类型声明。例如
namespace n1
{
class a<t>
{
class b{}
}
class c{}
}
namespace n2
{
using w = n1.a; //错误,不能命名泛型类型
using x = n1.a.b; //错误,不能命名泛型类型
using y = n1.a<int>; //ok,可以命名封闭构造类型
using z = n1.c; //ok
}
20.5.9特性
开放类型不能被用于特性内的任何地方。一个封闭构造类型可以被用作特性的实参,但不能被用作特性名,因为system.attribute不可能是泛型类声明的基类。
class a:attribute
{
public a(type t){…}
}
class b<t>: attribute{} //错误,不能将attribute用作基类
class list<t>
{
[a(typeof(t))] t t; //错误,在特性中有开放类型
}
class x
{
[a(typeof(list<int>))] int x; //ok,封闭构造类型
[b<int>] int y; //错误,无效的特性名字
}