泛型的约束
默认情况下,泛型的类型参数(parameter)可以使任何类型的。
如果只允许使用特性的类型参数(argument),就可以指定约束。
where T : base-class //表示T必须是某个父类的子类
where T : interface //表示T必须实现了某个接口
where T : class //表示T必须是引用类型
where T : struct //表示T必须是值类型,不包括可空值类型
where T : new() //表示类型里面必须有个无参构造函数
where U : T //表示U继承于T这个参数
泛型的约束 例子
class SomeClass{}
interface InterFace1{}
class GenericClass<T,U> where T:SomeClass,InterFace1 where U:new()
{..}
泛型的约束可以作用于类型或方法的定义。
IComparable
类型“int”不能用作泛型类型或方法“DemoClass.Maxstruct Nullable<T> where T:struct { .. }
// 本身意思表示可空值类型
裸类型约束
泛型类型的子类
泛型class可以有子类,在子类里,可以继续让父类类型参数保持开放
class Stack<T>{..}
class SpecialStack<T> : Stack<T>{..}
在子类里,也可以使用具体的类型来关闭(封闭)父类的类型参数
class IntStack:Stack<int>{..}
子类型也可以引入新的类型参数
class List<T>{..}
class KeyedList<T,Tkey> : List<T>{..}
技术上来讲,所有子类的类型参数都是新鲜的。你可以认为子类先把父类的类型参数(argument)给关闭了,然后又打开了。为这个先关闭后打开的类型参数(argument)带来新的名称或含义。
class List<T>{..}
class KeyedList<TElement,TKey> : List<TElment>{..}
自引用的泛型声明
在封闭类型参数(argument)的时候,该类型可以把它自己作为具体的类型。
class Foo
class Bar
静态数据
针对每一个封闭类型,静态数据是唯一的
类型参数和转换
C#的转换操作符支持下列转换:
数值转换
引用转换
装箱拆箱转换
自定义转换
决定采用的是哪种转换,发生在编译时,根据已知类型的操作数来决定
因为泛型只有在运行起来时才知道是什么类型
所以
StringBuilder Foo<T>(T arg)
{
if(arg is StringBuilder)
return (StringBuilder)arg; //编译不过去
}
使用as操作符
StringBuilder Foo<T>(T arg)
{
StringBuilder sb = arg as StringBuilder;
if(sb != null) return sb;
}
更常见的解决方法
return (StringBuilder)(object)arg;
拆箱的时候也可能发生歧义
int Foo<T>(T x) => (int)x; //Compile-time error
int Foo<T>(T x) => (int)(object)x;