为了更好的利用泛型,现将泛型的一些高级特性总结一下。
主要内容:
1. 泛型的协变和逆变
对于泛型参数(一般用T表示),指定了类型之后。就只能识别此类型,面向对象中的继承并不适用泛型参数,比如T指定为ClassA,尽管ClassB是ClassA的子类,也不能代替ClassA来作为泛型参数。
但是,利用泛型的协变和逆变之后,我们可以写出更加灵活的泛型代码,避免不必要的强制转型操作。
首先看下面的示例代码:
06 | publicdelegateTResult Print<T, TResult>(T arg); |
08 | staticvoidMain(string[] args) |
10 | ClassA a =newClassA(); |
11 | ClassB b =newClassB(); |
12 | ClassC c =newClassC(); |
14 | Print<ClassB, ClassB> p1 =newPrint<ClassB, ClassB>(Show); |
16 | Print<ClassC, ClassB> p2 = p1; |
17 | Console.WriteLine(p2(c).ToString()); |
19 | Print<ClassB, ClassA> p3 = p1; |
20 | Console.WriteLine(p3(b).ToString()); |
25 | staticClassB Show(ClassB b) |
33 | publicoverridestringToString() |
35 | return"This is Class A!"; |
41 | publicoverridestringToString() |
43 | return"This is Class B!"; |
49 | publicoverridestringToString() |
51 | return"This is Class C!"; |
上面有两处地方无法编译通过,分别是
1. p2的参数类型ClassC无法转换为p1的参数类型ClassB
2. p1的返回值类型ClassB无法转换为p3的返回值类型ClassA
上面这2点其实都是子类=>父类的过程,在C#中是很自然的转换。
通过泛型的协变和逆变,也可以实现上面的转换。
上面的代码只需改动一行就可以编译成功,即改变其中委托的定义,加入协变和逆变的关键字in和out
4 | publicdelegateTResult Print<inT,outTResult>(T arg); |
这里需要强调一点的是,不管协变和逆变,其本质都是子类代替父类,并没有违反面向对象的Liscov原则。
首先看逆变,因为参数类型由基类变成了派生类,那么函数内部的使用基类完成的操作都可以用派生类来替换。
再看协变,返回值由派生类变成了基类,那么函数内部原有返回派生类的操作都可以隐式转换为基类再返回。
通过协变和逆变,我们就可以不用修改函数(即上例中的Show函数)的前提下,使其支持多种泛型委托。
2. 泛型的参数的约束
泛型的约束不仅不会限制泛型的灵活性,反而会由于限制了泛型的类型,从而写出更有针对性的代码。
泛型的约束主要有3种:主要约束,次要约束,构造器约束。
2.1 主要约束
类型参数可以指定零个或一个主要约束。主要约束可以是一个引用类型,连个特殊的主要约束是class和struct
指定一个主要约束,相当于通知编译器:一个指定的类型实参要么是与约束类型相同的类型,要么是从约束类型派生的类型。
06 | staticvoidMain(string[] args) |
08 | GenericClassA<string> ga =newGenericClassA<string>(); |
09 | GenericClassA<int> ga1 =newGenericClassA<int>(); |
10 | GenericClassB<int> gb =newGenericClassB<int>(); |
11 | GenericClassB<string> gb1 =newGenericClassB<string>(); |
12 | GenericClassC<int> gc =newGenericClassC<int>(); |
13 | GenericClassC<string> gc1 =newGenericClassC<string>(); |
14 | GenericClassC<Stream> gc2 =newGenericClassC<Stream>(); |
15 | GenericClassC<FileStream> gc3 =newGenericClassC<FileStream>(); |
22 | classGenericClassA<T> where T :class |
27 | classGenericClassB<T> where T :struct |
32 | classGenericClassC<T> where T : Stream |
2.2 次要约束
类型参数可以指定零个或多个次要约束。主要约束代表一个接口类型。
指定一个次要约束,相当于通知编译器:一个指定的类型实参要么是实现了指定接口的一个类型。
06 | staticvoidMain(string[] args) |
09 | GenericClassD<string> gd =newGenericClassD<string>(); |
11 | GenericClassD<ClassD> gd1 =newGenericClassD<ClassD>(); |
13 | GenericClassD<Stream> gd2 =newGenericClassD<Stream>(); |
19 | classGenericClassD<T> where T : IDisposable, IComparable |
24 | classClassD : IDisposable, IComparable |
26 | #region IDisposable Members |
30 | thrownewNotImplementedException(); |
35 | #region IComparable Members |
37 | publicintCompareTo(objectobj) |
39 | thrownewNotImplementedException(); |
3.3 构造器约束
类型参数可以指定零个或一个构造器约束。
指定一个构造器约束,相当于通知编译器:一个指定的类型实参是实现了公共无参构造器的非抽象类型。
06 | staticvoidMain(string[] args) |
09 | GenericClassE<Stream> ge =newGenericClassE<Stream>(); |
11 | GenericClassE<FileStream> ge1 =newGenericClassE<FileStream>(); |
13 | GenericClassE<ClassE> ge2 =newGenericClassE<ClassE>(); |
18 | staticClassB Show(ClassB b) |
24 | classGenericClassE<T> where T :new() |