C#泛型约束
一般来讲,泛型的类型参数(parameter)可以是任意类型的。
如果只允许使用特定的类型参数(argument),就可以指定约束,
规定:
where T:base-class //必须是某个父类的子类
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()
{ . . . }
▢ 泛型的约束可以作用于类型或方法的定义。
如何约束:
public interface IComparable<T>
{
int CompareTo(T other);
}
static T Max<T>(T a, T b) where T : IComparable<T>
{
return a.CompareTo(b) > 0 ? a : b;
}
int z = Max(5, 10); //10
string last = Max("ant", "zoo"); //zoo
注意这里只是示意,你不能自己定义IComparable,
泛型类型的子类
▢ 泛型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<TElement>{ . . . }
自引用的泛型声明
▢ 在封闭类型参数(argument)的时候,该类型可以把自己作为具体的类型。
public interface IEquatable<T> { bool Equals(T obj);}
public class Balloon:IEquatable<Balloon>
{
public string Color{get;set;}
public int CC{get;set;}public bool Equals(Balloon b)
{
if(b==null) return false;
return b.Color==Color&&b.CC==CC;
}
}
class Foo<T> where T:IComparable<T>{ . . . }
class Bar<T> where T:Bar<T>{ . . .}
静态数据
▢ 针对每一个封闭类型,静态数据是唯一的
class Bob<T>{public static int Count;}
class Test
{
static void Main()
{
Console.WriteLine(++Bob<int>.Count); // 1
Console.WriteLine(++Bob<int>.Count); // 2
Console.WriteLine(++Bob<string>.Count); // 1
Console.WriteLine(++Bob<object>.Count); // 1
}
}
类型参数和转换
▢ C#的转换操作符支持下列转换:
▢ 数值转换
▢ 引用转换
▢ 装箱拆箱转换
▢ 自定义转换
▢ 决定采用的是那种转换,发生在编译时,根据已知类型的操作数来决定。
比如,这么写会产生歧义,编译器以为你是想自定义转换
StringBuilder Foo<T>(T arg)
{
if(arg is StringBuilder)
return (StringBuilder) arg; //Will not compile
. . .
}
最佳解决方案:
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;