接泛型五
20.8表达式和语句
某些表达式和语句的操作针对泛型进行了修改。这一节将介绍这些改变。
20.8.1默认值表达式
默认值表达式被用于获得一个类型的默认值(§5.2)。通常一个默认值表达式被用于类型参数,因为如果类型参数的是一个值类型或引用类型,它可能不是已经的。(不存在从null类型到类型参数的转换。)
primary-no-array-creation-expression:(基本无数组创建表达式:)
…
default-value-expression(默认值表达式)
default-value-expression:(默认值表达式:)
primary-expression . default (基本表达式 .default)
predefined-type . default(预定义类型. default)
如果一个基本表达式在一个默认值表达式中被使用,并且这个基本表达式不能分为一个类型,那么将会出现一个编译时错误。然而在§7.5.4.1中描述的规则也适用于形成E.default的构件。
如果默认值表达式的左边针对一个引用类型在运行时被计算,结果是将null转换到那个类型。如果默认值表达式的左边针对一个值类型在运行时被计算,其结果是值类型的默认值(§4.1.2)。
如果类型是一个具有类约束的引用类型或类型参数,默认值表达式是一个常量表达式(§7.15)。此外,如果类型是下列值之一,默认值表达式是一个常量表达式:sbyte、byte、 short、ushort、 int 、 uint 、long 、ulong 、 char 、float 、double 、decimal 或bool。
20.8.2对象创建表达式
对象常见表达式的类型可以是一个类型参数。当类型参数被作为对象创建表达式中的类型而指定时,下面两个条件必须具备,否则将会出现编译时错误
实参列表必须删除
必须为类型参数而指定new()形式的构造函数约束
通过创建一个类型参数被绑定到的运行时类型的实例,并调用该类型的默认构造函数,就可以执行对象创建表达式。运行时类型可以是引用或者值类型。
20.8.3运算符的类型
typeof运算符可以用于类型参数。其结果是被绑定到类型参数的运行时类型的System.Type对象。typeof运算符也可以被用于构造类型。
class X <T>
{
public static void PrintTypes()
{
Console.WriteLine(typeof(T).FullName);
Console.WriteLine(typeof(X<X<T>>).FullName);
}
}
class M
{
static void Main()
{
X<int>.PrintTypes();
}
}
先前的程序将打印如下。
System.Int32
X<X<Sytem.Int32>>
Typeof运算符不能用于没有指定类型实参的泛型类型声明的名字。
class X<T>{…}
class M
{
static void Main()
{
Type t = typeof(X); //错误,X需要类型实参
}
}
20.8.4引用相等运算符
如果T由一个类约束而约束,引用类型相等运算符可以被用于比较类型参数T的值。
引用类型相等运算符的用法可以让类型参数T的实参很容易地与其他为null的实参进行比较,即使T没有类约束也如此。在运行时,如果T是一个值类型,比较的结果将是false。
下面的例子检查一个非约束类型参数类型的实参是否是null。
class C<T>
{
void F(T x)
{
if(x==null) thow new ArgumentNullException();
…
}
}
即使是T可以表示一个值类型,x==null构件也是允许的,并且当T是值类型时,其结果被简单的定义为false。
20.8.5 is运算符
在开放类型上的is运算符操作遵循通常的规则(§7.9.9)。如果e或T的编译时类型是一个开放类型,那么在运行时对于e和T将总是执行动态类型检查。
20.8.6as运算符
只要T有一个类约束,类型参数T可被用在as运算符的右边。这种限制是需要的,因为值null可能被作为运算符的结果返回。
class X
{
public T F<T>(object o) where T:Attribute
{
return o as T; //ok,T有一个类约束
}
public T G<T>(object o)
{
return o as T; //错误,T没有约束
}
}
在as运算符(§7.9.10)的当前规范中,对于表达式e as T最后一点表明,如果从e的编译时类型到T,不存在有效的显式引用转换,将会出现编译时错误。对于泛型,这条规则稍微作了修改。如果E的编译时类型或T是一个开放类型,在这种情况下将不会出现编译时错误;相反,运行时检查将会执行。
20.8.7异常语句
对于开放类型,throw(§8.9.5)和try(§8.10)的通常规则是适用的。
只要类型参数具有System.Exeption异常(或子类具有)作为类约束,那么throw语句可以被用作其类型有一个类型参数给定的表达式。
只要类型参数System.Exception(或子类子类具有)作为类约束,那么在catch语句中的命名的类型可能是一个类型参数。
20.8.8 lock语句
lock语句可以被用作其类型由一个类型参数给定的表达式。如果表达式的运行时类型是一个值类型,lock将没有效果(因为对于装箱值不能有任何其他的引用)。
20.8.9 using 语句
using 语句(§8.13)遵循通常的规则:表达式必须被隐式的转换到System.IDisposable。如果类型参数通过System.IDisposable而约束,那么该类型的表达式可以使用using 语句。
20.8.10 foreach语句
给定如下形式的foreach语句
foreach(ElementType element in collection) statement
如果集合表达式是一个没有实现集合模式的类型,但为每个类型T实现了构造接口System.Collections.Generic.IEnumerable<T>,那么foreach语句的扩展是
IEnumerator<T> enumerator = ((IEnuemrable<T>)(collection).GetEnumerator();
try
{
where (enumerator.MoveNext()){
ElementType element = (ElementType)enumerator.Current;
statement;
}
}
finally{
enumerator.Dispose();
}
20.9查找规则修订
泛型修改了用于查找和绑定名字的某些基本规则。下面几节在考虑泛型的情况下,重新叙述了所有的基本名字查找规则。
20.9.1命名空间和类型名字
如下内容可替换§3.8。
在C#程序中有几个上下文需要指定命名空间或者类型名字。任何形式的名字都可以由一个或多个由“.”标记分隔的标识符号组成。
namespace-name:(命名空间名字:)
namespace-or-type-name(命名空间或类型名字)
type-name:(类型名字:)
namespace-or-type-name(命名空间或类型名字)
namespace-or-type-name:(命名空间或类型名字:)
identifier type-argument-list opt(标识符 类型实参列表可选)
namespace-or-type-name . identifier type-argument-list opt( 命名空间或类型名字. 标识符