为了更好的利用泛型,现将泛型的一些高级特性总结一下。
主要内容:
- 泛型的协变和逆变
- 泛型的参数的约束
1. 泛型的协变和逆变
对于泛型参数(一般用T表示),指定了类型之后。就只能识别此类型,面向对象中的继承并不适用泛型参数,比如T指定为ClassA,尽管ClassB是ClassA的子类,也不能代替ClassA来作为泛型参数。
但是,利用泛型的协变和逆变之后,我们可以写出更加灵活的泛型代码,避免不必要的强制转型操作。
首先看下面的示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
using System;
class CLRviaCSharp_14
{ // 泛型委托,其中委托的参数和返回值都是泛型
public delegate TResult Print<T, TResult>(T arg);
static void Main( string [] args)
{
ClassA a = new ClassA();
ClassB b = new ClassB();
ClassC c = new ClassC();
Print<ClassB, ClassB> p1 = new Print<ClassB, ClassB>(Show);
// 此处无法赋值,会报错
Print<ClassC, ClassB> p2 = p1;
Console.WriteLine(p2(c).ToString());
// 此处无法赋值,会报错
Print<ClassB, ClassA> p3 = p1;
Console.WriteLine(p3(b).ToString());
Console.ReadKey();
}
static ClassB Show(ClassB b)
{
return (ClassB)b;
}
} class ClassA
{ public override string ToString()
{
return "This is Class A!" ;
}
} class ClassB : ClassA
{ public override string ToString()
{
return "This is Class B!" ;
}
} class ClassC : ClassB
{ public override string ToString()
{
return "This is Class C!" ;
}
} |
上面有两处地方无法编译通过,分别是
1. p2的参数类型ClassC无法转换为p1的参数类型ClassB
2. p1的返回值类型ClassB无法转换为p3的返回值类型ClassA
上面这2点其实都是 子类=>父类 的过程,在C#中是很自然的转换。
通过泛型的协变和逆变,也可以实现上面的转换。
上面的代码只需改动一行就可以编译成功,即改变其中委托的定义,加入协变和逆变的关键字in和out
1
2
3
4
|
// 泛型委托,其中委托的参数和返回值都是泛型 // in表示逆变, 即输入参数的类型可由基类改为派生类 // out表示协变,即返回值类型可以由派生类改为基类 public delegate TResult Print< in T, out TResult>(T arg);
|
这里需要强调一点的是,不管协变和逆变,其本质都是子类代替父类,并没有违反面向对象的Liscov原则。
首先看逆变,因为参数类型由基类变成了派生类,那么函数内部的使用基类完成的操作都可以用派生类来替换。
再看协变,返回值由派生类变成了基类,那么函数内部原有返回派生类的操作都可以隐式转换为基类再返回。
通过协变和逆变,我们就可以不用修改函数(即上例中的Show函数)的前提下,使其支持多种泛型委托。
2. 泛型的参数的约束
泛型的约束不仅不会限制泛型的灵活性,反而会由于限制了泛型的类型,从而写出更有针对性的代码。
泛型的约束主要有3种:主要约束,次要约束,构造器约束。
2.1 主要约束
类型参数可以指定零个或一个主要约束。主要约束可以是一个引用类型,连个特殊的主要约束是class和struct
指定一个主要约束,相当于通知编译器:一个指定的类型实参要么是与约束类型相同的类型,要么是从约束类型派生的类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
using System;
using System.IO;
class CLRviaCSharp_14
{ static void Main( string [] args)
{
GenericClassA< string > ga = new GenericClassA< string >(); // 正确
GenericClassA< int > ga1 = new GenericClassA< int >(); // 错误
GenericClassB< int > gb = new GenericClassB< int >(); // 正确
GenericClassB< string > gb1 = new GenericClassB< string >(); // 错误
GenericClassC< int > gc = new GenericClassC< int >(); // 错误
GenericClassC< string > gc1 = new GenericClassC< string >(); // 错误
GenericClassC<Stream> gc2 = new GenericClassC<Stream>(); // 正确
GenericClassC<FileStream> gc3 = new GenericClassC<FileStream>(); // 正确
Console.ReadKey();
}
} // T必须是引用类型 class GenericClassA<T> where T : class
{ } // T必须是值类型 class GenericClassB<T> where T : struct
{ } // T必须是Stream类型或者Stream类型的派生类型 class GenericClassC<T> where T : Stream
{ } |
2.2 次要约束
类型参数可以指定零个或多个次要约束。主要约束代表一个接口类型。
指定一个次要约束,相当于通知编译器:一个指定的类型实参要么是实现了指定接口的一个类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
using System;
using System.IO;
class CLRviaCSharp_14
{ static void Main( string [] args)
{
// 错误,string实现了IComparable但是没有实现IDisposable
GenericClassD< string > gd = new GenericClassD< string >();
// 正确,ClassD既实现了IDisposable也实现了IComparable
GenericClassD<ClassD> gd1 = new GenericClassD<ClassD>();
// 错误,Stream实现了IDisposable但是没有实现IComparable
GenericClassD<Stream> gd2 = new GenericClassD<Stream>();
Console.ReadKey();
}
} class GenericClassD<T> where T : IDisposable, IComparable
{ } class ClassD : IDisposable, IComparable
{ #region IDisposable Members
public void Dispose()
{
throw new NotImplementedException();
}
#endregion
#region IComparable Members
public int CompareTo( object obj)
{
throw new NotImplementedException();
}
#endregion
} |
3.3 构造器约束
类型参数可以指定零个或一个构造器约束。
指定一个构造器约束,相当于通知编译器:一个指定的类型实参是实现了公共无参构造器的非抽象类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
using System;
using System.IO;
class CLRviaCSharp_14
static void Main( string [] args)
{
// 错误,Stream是抽象类型
GenericClassE<Stream> ge = new GenericClassE<Stream>();
// 错误,FileStream没有公共无参构造函数
GenericClassE<FileStream> ge1 = new GenericClassE<FileStream>();
// 正确,ClassE有公共默认无参构造函数,并且也是非抽象类型
GenericClassE<ClassE> ge2 = new GenericClassE<ClassE>();
Console.ReadKey();
}
static ClassB Show(ClassB b)
{
return (ClassB)b;
}
} class GenericClassE<T> where T : new ()
{ } class ClassE
{ } |