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

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

主要内容:

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

1. 泛型的协变和逆变

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

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

首先看下面的示例代码:

01 using System;
02  
03 class CLRviaCSharp_14
04 {
05     // 泛型委托,其中委托的参数和返回值都是泛型
06     public delegate TResult Print<T, TResult>(T arg);
07  
08     static void Main(string[] args)
09     {
10         ClassA a = new ClassA();
11         ClassB b = new ClassB();
12         ClassC c = new ClassC();
13  
14         Print<ClassB, ClassB> p1 = new Print<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     static ClassB Show(ClassB b)
26     {
27         return (ClassB)b;
28     }
29 }
30  
31 class ClassA
32 {
33     public override string ToString()
34     {
35         return "This is Class A!";
36     }
37 }
38  
39 class ClassB : ClassA
40 {
41     public override string ToString()
42     {
43         return "This is Class B!";
44     }
45 }
46  
47 class ClassC : ClassB
48 {
49     public override string ToString()
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 public delegate TResult Print<in T, out TResult>(T arg);

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

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

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

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

2. 泛型的参数的约束

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

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

2.1 主要约束

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

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

01 using System;
02 using System.IO;
03  
04 class CLRviaCSharp_14
05 {
06     static void Main(string[] args)
07     {
08         GenericClassA<string> ga = new GenericClassA<string>();              // 正确
09         GenericClassA<int> ga1 = new GenericClassA<int>();                   // 错误
10         GenericClassB<int> gb = new GenericClassB<int>();                    // 正确
11         GenericClassB<string> gb1 = new GenericClassB<string>();             // 错误
12         GenericClassC<int> gc = new GenericClassC<int>();                    // 错误
13         GenericClassC<string> gc1 = new GenericClassC<string>();             // 错误
14         GenericClassC<Stream> gc2 = new GenericClassC<Stream>();             // 正确
15         GenericClassC<FileStream> gc3 = new GenericClassC<FileStream>();     // 正确
16  
17         Console.ReadKey();
18     }
19 }
20  
21 // T必须是引用类型
22 class GenericClassA<T> where T : class
23 {   
24 }
25  
26 // T必须是值类型
27 class GenericClassB<T> where T : struct
28 {
29 }
30  
31 // T必须是Stream类型或者Stream类型的派生类型
32 class GenericClassC<T> where T : Stream
33 {
34 }
2.2 次要约束

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

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

01 using System;
02 using System.IO;
03  
04 class CLRviaCSharp_14
05 {
06     static void Main(string[] args)
07     {
08         // 错误,string实现了IComparable但是没有实现IDisposable
09         GenericClassD<string> gd = new GenericClassD<string>();
10         // 正确,ClassD既实现了IDisposable也实现了IComparable
11         GenericClassD<ClassD> gd1 = new GenericClassD<ClassD>();
12         // 错误,Stream实现了IDisposable但是没有实现IComparable
13         GenericClassD<Stream> gd2 = new GenericClassD<Stream>();
14  
15         Console.ReadKey();
16     }
17 }
18  
19 class GenericClassD<T> where T : IDisposable, IComparable
20 {
21      
22 }
23  
24 class ClassD : IDisposable, IComparable
25 {
26     #region IDisposable Members
27  
28     public void Dispose()
29     {
30         throw new NotImplementedException();
31     }
32  
33     #endregion
34  
35     #region IComparable Members
36  
37     public int CompareTo(object obj)
38     {
39         throw new NotImplementedException();
40     }
41  
42     #endregion
43 }
3.3 构造器约束

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值