12.泛型
面向对象支持“代码重用”,派生类不仅继承了基类的所有能力,还能重写虚方法和添加新方法以定制派生类的行为。而泛型支持的是“算法重用”。CLR允许创建泛型引用类型、泛型值类型、泛型接口和泛型委托。CLR允许在(非泛型和泛型)引用类型、值类型和接口中定义泛型方法,比如:System.Array类就提供了大量静态泛型方法。
泛型的好处:避免值类型进行装箱、增加类型安全性。
12.1FCL中的泛型
泛型主要用于集合类,泛型集合类主要在System.Collections.Generic;
。比如:封装了泛型列表算法的FCL类称为List<T>,public class List<T>...
中的T是**(泛型)类型参数**,List<String>
中的String是**(泛型)类型实参**。
12.2泛型基础结构
开放类型和封闭类型
开放类型:具有类型参数的类型,禁止构造开放类型的实例。
封闭类型:具有具体类型参数的类型,允许构造封闭类型的实例。
举例:
internal static class OpenAndCloseType
{
public sealed class TestType<T> { }
public static void Go()
{
object o = null;
Type type = typeof(TestType<>); // 开放类型
o = CreateInstance(type);
Console.WriteLine();
type = typeof(TestType<Guid>); // 封闭类型
o = CreateInstance(type);
Console.WriteLine("Type:" + o.GetType());
}
private static Object CreateInstance(Type type)
{
object o = null;
try{
o = Activator.CreateInstance(type);
Console.WriteLine("Create instance of {0}", type);
}
catch(ArgumentException e){
Console.WriteLine(e.Message);
}
return o;
}
}
输出:
无法创建OpenAndCloseType+TestType`1[T]的实例,因为Type.ContainsGenericParameters=True。
Create instance of Chapter12Genericity.OpenAndCloseType+TestType`1[System.Guid]
Type:Chapter12Genericity.OpenAndCloseType+TestType`1[System.Guid]
从输出可看出,类型名以“`”字符和一个数字结尾,数字代表类型要求的类型参数的个数。
另外,如果不使用try-catch的话,就无法运行,直接弹出异常窗口,如下:
使用泛型类型并指定类型实参,也就定义了一个新的封闭类型对象。每个封闭类型对象都有自己的静态字段,比如:List<T>定义了一个静态字段,该字段不会在一个List<String>和一个List<int>之间共享。
泛型类型和继承
指定类型实参不影响继承层次结构:指定类型实参所生成的新的类型对象 从 泛型类型派生自的那个类型 派生,比如:由于List<T>从Object派生,所以List<int>也从Object派生。
书P241的两个例子挺好的,可以看看。
12.3泛型接口
// 指定了类型实参地实现了泛型接口IEnumerator<T>,IEnumerator<T>定义在FCL的System.Collections.Generic中。
internal sealed class Triangle : IEnumerator<int>
{
int[] mVertices;
public int CurrentVertices { get { } }
}
// 未指定类型实参地实现了泛型接口IEnumerator<T>
internal sealed class ArrayEnum<T> : IEnumerator<T>
{
T[] mArray;
public T CurrentValue { get { } }
}
12.4泛型委托
// 自定义委托类型
public delegate TReturn CallMe<TReturn, TKey, TValue>(TKey key, TValue value);
CLR支持自定义泛型委托,但建议尽量使用FCL预定义的泛型Action和Func委托。
12.5泛型方法
泛型方法的类型参数可以有两种来源:1.来自类型;2.来自方法。不管类型参数来自谁,类型参数都能应用于1.方法参数;2.方法返回值;3.方法内部局部变量。
来自类型的那种已经举了很多例子了,再举个来自方法的例子:
public static void Swap<T>(ref T num1, ref T num2) // 参数必须加ref,不然该Swap函数就没作用
{
T temp = num1;
num1 = num2;
num2 = temp;
}
// 在Main种调用
int n1 = 1, n2 = 2;
Swap<int>(ref n1, ref n2);
C#编译器支持在调用泛型方法时进行类型推断,调用Swap函数可以这样写:Swap(ref n1, ref n2);
另外,虽然属性、索引器、事件、操作符方法、构造器和终结器本质上都是方法,但它们都不能像方法这样定义类型参数,但可以使用类型的类型参数。
12.6约束
约束的作用是:通过一个约束准则来限制 能指定为类型实参的类型 数量,举例:传入的T要实现IComparable<T>接口才可以。
public T Max<T>(T o1, T o2) where T : IComparable<T>
{
if (o1.CompareTo(o2) < 0) return o2;
return o1;
}
约束的注意点:
1.约束可应用于泛型类型的类型参数,也可应用于泛型方法的类型参数(上面这个例子)。
2.CLR不允许通过类型参数名称的不同或约束的不同对泛型类型或泛型方法进行重载,只能通过类型参数个数的不同对泛型类型或泛型方法进行重载。
3.重写的虚泛型方法必须指定相同数量的类型参数(参数的名字可以不同),且这些类型参数会自动继承基类的约束(重写后的方法不能再指定额外的约束)。对于实现接口里的方法,一样。
有三类约束:1.主要约束,2.次要约束,3.构造器约束
1.主要约束
类型参数可以指定零个或一个主要约束,指定主要约束就相当于告诉编译器:类型实参要么是与约束类型相同的类型,要么是从约束类型派生的类型。主要约束只能约束引用类型(因为值类型隐式密封,也就不能从值类型派生,所以,如果主要约束是值类型,就相当于只支持该值类型,这还不如不要泛型呢!),举例:
public sealed class PrimaryConstraintOfStream<T> where T : System.IO.Stream
{
public void M(T stream) { stream.Close(); }
}
// 类型实参指定为Stream类型或者Stream的派生类型(比如:FileStream)都行
1’.两个特殊的主要约束:class和struct
class告诉编译器类型实参必须是引用类型:
public sealed class PrimaryConstraintOfClass<T> where T : class
{
// 正确,因为class限制了T必须是引用类型,引用类型可为null,值类型不能为null,没有class就不对了
public void M() { T temp = null; }
}
struct告诉编译器类型实参必须是值类型:(struct不是值类型,是关键字,所以主要约束里的结论仍然正确)
public sealed class PrimaryConstraintOfStruct<T> where T : struct
{
// 正确,因为struct限制了T必须是值类型,值类型都有公共无参构造器,而部分引用类型没有,没有struct就不对了
public void M() { T temp = new T(); }
}
2.次要约束
类型参数可以指定零个或多个次要约束,指定次要约束就相当于告诉编译器:类型实参必须是 实现了指定的接口 的类型。次要约束主要约束接口类型(其他次要约束就不考虑了,用得很少)。举例:标题为“12.6约束”的第一个例子。
3.构造器约束
类型参数可以指定零个或一个构造器约束,指定构造器约束就相当于告诉编译器:类型实参必须是 实现了公共无参构造器 的类型。错误使用:同时使用构造器约束和struct约束,没意义,因为所有值类型都隐式提供了公共无参构造器。而部分引用类型没有提供公共无参构造器,所以构造器约束只能约束引用类型,举例:
public sealed class ConstructorConstraint<T> where T : class, new()
{
public void M() { T temp = new T(); }
}
约束这节的补充内容:1.T temp = default(T);
中的default关键字告诉编译器,如果T是引用类型,temp设为null;如果T是值类型,temp的所有值都设为0;2.泛型类型变量不支持很多运算符,比如:+ - * / ++ += < >等等,因为编译时确定不了泛型类型变量的具体类型,这是CLR的限制。。。