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

简介:

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

主要内容:

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

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
{   
}
标签:  CLR via C#笔记


本文转自wang_yb博客园博客,原文链接:http://www.cnblogs.com/wang_yb/archive/2011/07/25/2116130.html,如需转载请自行联系原作者
目录
相关文章
|
21天前
|
C# Python
C# 笔记1 - 操作目录
C# 笔记1 - 操作目录
30 0
|
21天前
|
存储 安全 编译器
C# 11.0中的泛型属性:类型安全的新篇章
【1月更文挑战第23天】C# 11.0引入了泛型属性的概念,这一新特性为开发者提供了更高级别的类型安全性和灵活性。本文将详细探讨C# 11.0中泛型属性的工作原理、使用场景以及它们对现有编程模式的改进。通过深入了解泛型属性,开发者将能够编写更加健壮、可维护的代码,并充分利用C#语言的最新发展。
|
9月前
|
存储 算法 安全
C#三十二 泛型的理解和使用
C#三十二 泛型的理解和使用
22 0
|
21天前
|
存储 安全 Java
34.C#:listT泛型集合
34.C#:listT泛型集合
25 1
|
21天前
|
开发框架 安全 .NET
C# .NET面试系列三:集合、异常、泛型、LINQ、委托、EF!
<h2>集合、异常、泛型、LINQ、委托、EF! #### 1. IList 接口与 List 的区别是什么? IList 接口和 List 类是C#中集合的两个相关但不同的概念。下面是它们的主要区别: <b>IList 接口</b> IList 接口是C#中定义的一个泛型接口,位于 System.Collections 命名空间。它派生自 ICollection 接口,定义了一个可以通过索引访问的有序集合。 ```c# IList 接口包含一系列索引化的属性和方法,允许按索引访问、插入、移除元素等。 由于是接口,它只定义了成员的契约,而不提供具体的实现。类似于 IEnumera
198 2
|
21天前
|
C# Python
C# 笔记3 - 重载一系列像python那样的print()方法
C# 笔记3 - 重载一系列像python那样的print()方法
30 1
|
21天前
|
存储 C# C++
C# 笔记2 - 数组、集合与与文本文件处理
C# 笔记2 - 数组、集合与与文本文件处理
52 0
|
21天前
|
存储 安全 算法
C# 泛型:类型参数化的强大工具
【1月更文挑战第7天】本文将深入探讨C#语言中的泛型编程,包括泛型的定义、用途、优势以及实际应用。通过类型参数化,泛型允许开发者编写更加灵活且可重用的代码,同时提高程序的类型安全性和性能。本文将通过示例代码和详细解释,帮助读者更好地理解泛型在C#中的重要性和实用性。
|
21天前
|
存储 Java 编译器
【从Java转C#】第五章:泛型
【从Java转C#】第五章:泛型
|
9月前
|
机器学习/深度学习 存储 缓存
一文带你搞懂C#泛型
泛型是.net 2.0中提供的新特性,是框架的一种升级,用于处理用一个事物来代替多种不同需求的情况。下面我们就一块来看一下具体的讲解吧。