《CLR Via C# 第3版》笔记之(十四) - 泛型高级

本文深入探讨了C#中泛型的高级特性,包括协变和逆变的应用,以及如何通过不同类型约束(如主要约束、次要约束和构造器约束)来增强泛型代码的灵活性与针对性。

为了更好的利用泛型,现将泛型的一些高级特性总结一下。

主要内容:

  • 泛型的协变和逆变
  • 泛型的参数的约束

1. 泛型的协变和逆变

对于泛型参数(一般用T表示),指定了类型之后。就只能识别此类型,面向对象中的继承并不适用泛型参数,比如T指定为ClassA,尽管ClassB是ClassA的子类,也不能代替ClassA来作为泛型参数。

但是,利用泛型的协变和逆变之后,我们可以写出更加灵活的泛型代码,避免不必要的强制转型操作。

首先看下面的示例代码:

01 usingSystem;
02
03 classCLRviaCSharp_14
04 {
05 // 泛型委托,其中委托的参数和返回值都是泛型
06 publicdelegateTResult Print<T, TResult>(T arg);
07
08 staticvoidMain(string[] args)
09 {
10 ClassA a =newClassA();
11 ClassB b =newClassB();
12 ClassC c =newClassC();
13
14 Print<ClassB, ClassB> p1 =newPrint<ClassB, ClassB>(Show);
15 // 此处无法赋值,会报错
16 Print<ClassC, ClassB> p2 = p1;
17 Console.WriteLine(p2(c).ToString());
18 // 此处无法赋值,会报错
19 Print<ClassB, ClassA> p3 = p1;
20 Console.WriteLine(p3(b).ToString());
21
22 Console.ReadKey();
23 }
24
25 staticClassB Show(ClassB b)
26 {
27 return(ClassB)b;
28 }
29 }
30
31 classClassA
32 {
33 publicoverridestringToString()
34 {
35 return"This is Class A!";
36 }
37 }
38
39 classClassB : ClassA
40 {
41 publicoverridestringToString()
42 {
43 return"This is Class B!";
44 }
45 }
46
47 classClassC : ClassB
48 {
49 publicoverridestringToString()
50 {
51 return"This is Class C!";
52 }
53 }

上面有两处地方无法编译通过,分别是

1. p2的参数类型ClassC无法转换为p1的参数类型ClassB

2. p1的返回值类型ClassB无法转换为p3的返回值类型ClassA

上面这2点其实都是子类=>父类的过程,在C#中是很自然的转换。

通过泛型的协变和逆变,也可以实现上面的转换。

上面的代码只需改动一行就可以编译成功,即改变其中委托的定义,加入协变和逆变的关键字in和out

1 // 泛型委托,其中委托的参数和返回值都是泛型
2 // in表示逆变, 即输入参数的类型可由基类改为派生类
3 // out表示协变,即返回值类型可以由派生类改为基类
4 publicdelegateTResult Print<inT,outTResult>(T arg);

这里需要强调一点的是,不管协变和逆变,其本质都是子类代替父类,并没有违反面向对象的Liscov原则。

首先看逆变,因为参数类型由基类变成了派生类,那么函数内部的使用基类完成的操作都可以用派生类来替换。

再看协变,返回值由派生类变成了基类,那么函数内部原有返回派生类的操作都可以隐式转换为基类再返回。

通过协变和逆变,我们就可以不用修改函数(即上例中的Show函数)的前提下,使其支持多种泛型委托。

2. 泛型的参数的约束

泛型的约束不仅不会限制泛型的灵活性,反而会由于限制了泛型的类型,从而写出更有针对性的代码。

泛型的约束主要有3种:主要约束,次要约束,构造器约束。

2.1 主要约束

类型参数可以指定零个或一个主要约束。主要约束可以是一个引用类型,连个特殊的主要约束是class和struct

指定一个主要约束,相当于通知编译器:一个指定的类型实参要么是与约束类型相同的类型,要么是从约束类型派生的类型。

01 usingSystem;
02 usingSystem.IO;
03
04 classCLRviaCSharp_14
05 {
06 staticvoidMain(string[] args)
07 {
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>();// 正确
16
17 Console.ReadKey();
18 }
19 }
20
21 // T必须是引用类型
22 classGenericClassA<T> where T :class
23 {
24 }
25
26 // T必须是值类型
27 classGenericClassB<T> where T :struct
28 {
29 }
30
31 // T必须是Stream类型或者Stream类型的派生类型
32 classGenericClassC<T> where T : Stream
33 {
34 }
2.2 次要约束

类型参数可以指定零个或多个次要约束。主要约束代表一个接口类型。

指定一个次要约束,相当于通知编译器:一个指定的类型实参要么是实现了指定接口的一个类型。

01 usingSystem;
02 usingSystem.IO;
03
04 classCLRviaCSharp_14
05 {
06 staticvoidMain(string[] args)
07 {
08 // 错误,string实现了IComparable但是没有实现IDisposable
09 GenericClassD<string> gd =newGenericClassD<string>();
10 // 正确,ClassD既实现了IDisposable也实现了IComparable
11 GenericClassD<ClassD> gd1 =newGenericClassD<ClassD>();
12 // 错误,Stream实现了IDisposable但是没有实现IComparable
13 GenericClassD<Stream> gd2 =newGenericClassD<Stream>();
14
15 Console.ReadKey();
16 }
17 }
18
19 classGenericClassD<T> where T : IDisposable, IComparable
20 {
21
22 }
23
24 classClassD : IDisposable, IComparable
25 {
26 #region IDisposable Members
27
28 publicvoidDispose()
29 {
30 thrownewNotImplementedException();
31 }
32
33 #endregion
34
35 #region IComparable Members
36
37 publicintCompareTo(objectobj)
38 {
39 thrownewNotImplementedException();
40 }
41
42 #endregion
43 }
3.3 构造器约束

类型参数可以指定零个或一个构造器约束。

指定一个构造器约束,相当于通知编译器:一个指定的类型实参是实现了公共无参构造器的非抽象类型。

01 usingSystem;
02 usingSystem.IO;
03
04 classCLRviaCSharp_14
05
06 staticvoidMain(string[] args)
07 {
08 // 错误,Stream是抽象类型
09 GenericClassE<Stream> ge =newGenericClassE<Stream>();
10 // 错误,FileStream没有公共无参构造函数
11 GenericClassE<FileStream> ge1 =newGenericClassE<FileStream>();
12 // 正确,ClassE有公共默认无参构造函数,并且也是非抽象类型
13 GenericClassE<ClassE> ge2 =newGenericClassE<ClassE>();
14
15 Console.ReadKey();
16 }
17
18 staticClassB Show(ClassB b)
19 {
20 return(ClassB)b;
21 }
22 }
23
24 classGenericClassE<T> where T :new()
25 {
26 }
27
28 classClassE
29 {
30 }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值