《CLR Via C# 第3版》笔记之(十四) - 泛型高级
来源:互联网 发布:macbook专业修图软件 编辑:程序博客网 时间:2024/05/02 00:06
为了更好的利用泛型,现将泛型的一些高级特性总结一下。
主要内容:
- 泛型的协变和逆变
- 泛型的参数的约束
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
}
- 《CLR Via C# 第3版》笔记之(十四) - 泛型高级
- clr via C#笔记(3)
- CLR via C#(第3版)学习笔记
- CLR via C#笔记
- 框架设计(第2版):CLR Via C#
- 框架设计(第2版):CLR Via C#
- 框架设计(第2版)CLR Via C#
- clr via C#笔记(4)
- clr via C#笔记(1)
- clr via C#笔记(2)
- clr via C#笔记(5)
- clr via C#笔记(6)
- CLR via C# 阅读 笔记
- CLR via C#(第3 版)
- CLR VIA C# 泛型和接口
- CLR via C# 之旅
- CLR via C# 学习笔记(2012/3/4)
- CLR via C# 学习笔记(2012/3/6)
- JS数字键盘
- timer
- Java Web Project 转MVN Project
- split()中参数不能直接用.
- [C#]在WinForm下使用HttpWebRequest上传文件并显示进度
- 《CLR Via C# 第3版》笔记之(十四) - 泛型高级
- Windows下C中__assume的作用
- 小孩拉肚子不能吃什么?
- UIImageview 点击事件
- 使用Cygwin模拟Linux环境安装配置运行基于单机的Hadoop
- 2011年8月份总结
- C#.net Winform获取文件路径
- HTML跳转方法大全
- format:自己写的format函数