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

主要内容:

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

1. 泛型的协变和逆变

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

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

首先看下面的示例代码:

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

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

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

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

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

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

2. 泛型的参数的约束

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

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

2.1 主要约束

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

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

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 次要约束

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

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

using System;
using System.IO;class CLRviaCSharp_14
{static void Main(string[] args){// 错误,string实现了IComparable但是没有实现IDisposableGenericClassD<string> gd = new GenericClassD<string>();// 正确,ClassD既实现了IDisposable也实现了IComparableGenericClassD<ClassD> gd1 = new GenericClassD<ClassD>();// 错误,Stream实现了IDisposable但是没有实现IComparableGenericClassD<Stream> gd2 = new GenericClassD<Stream>();Console.ReadKey();}
}class GenericClassD<T> where T : IDisposable, IComparable
{}class ClassD : IDisposable, IComparable
{#region IDisposable Memberspublic void Dispose(){throw new NotImplementedException();}#endregion#region IComparable Memberspublic int CompareTo(object obj){throw new NotImplementedException();}#endregion
}

3.3 构造器约束

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

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

using System;
using System.IO;class CLRviaCSharp_14static 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
{
}

转载于:https://www.cnblogs.com/wang_yb/archive/2011/07/25/2116130.html

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

  1. 《深入浅出DPDK》读书笔记(十四):DPDK应用篇(DPDK与网络功能虚拟化:NFV、VNF、IVSHMEM、Virtual BRAS“商业案例”)

    Table of Contents DPDK应用篇 DPDK与网络功能虚拟化 157.网络功能虚拟化 13.1.1起源 158.发展 159.OPNFV与DPDK NFV的部署 160.NFV的部署 ...

  2. 【CS231n】斯坦福大学李飞飞视觉识别课程笔记(十四):神经网络笔记2(上)

    [CS231n]斯坦福大学李飞飞视觉识别课程笔记 由官方授权的CS231n课程笔记翻译知乎专栏--智能单元,比较详细地翻译了课程笔记,我这里就是参考和总结. [CS231n]斯坦福大学李飞飞视觉识别课 ...

  3. Windows保护模式学习笔记(十四)—— 阶段测试

    Windows保护模式学习笔记(十四)-- 阶段测试 题目一 解题步骤 题目二 解题步骤 题目一 描述:给定一个线性地址,和长度,读取内容 int ReadMemory(OUT BYTE* buffe ...

  4. OpenCV学习笔记(十四):重映射:remap( )

    OpenCV学习笔记(十四):重映射:remap( ) 图像的坐标映射是通过原图像与目标图像之间建立一种映射关系,这种映射关系有两种,一种是计算原图像任意像素在映射后图像的坐标位置,另一种是计算变换后 ...

  5. QT学习笔记(十四):QLayout的属性介绍

    QT学习笔记(十四):QLayout的属性介绍 主要包括QBoxLayout.和QGridLayout以及QFormLayout等的参数类似. 我主要说明一下QGridLayout在QtDesigne ...

  6. MATLAB学习笔记(十四)

    MATLAB学习笔记(十四) 一.线性方程组求解 1.1 直接法 1.1.1 利用左除运算符 1.1.2 利用矩阵分解 1.2 迭代法 1.2.1 雅可比(Jacobi)迭代法 1.2.2 高斯-赛德 ...

  7. python数据挖掘学习笔记】十四.Scipy调用curve_fit实现曲线拟合

    #2018-03-28 10:02:08 March Wednesday the 13 week, the 087 day SZ SSMR python数据挖掘学习笔记]十四.Scipy调用curve ...

  8. 品优购项目笔记(十四):微信支付

    品优购项目笔记(十四) 订单 订单三张表关系 提交订单 二维码 介绍 优势 容错级别 qrious二维码生成插件 微信支付 微信支付流程 项目支付流程 生成支付链接 查询是否支付成功 订单 订单三张表 ...

  9. Ruby‘s Adventrue游戏制作笔记(十四)Unity播放游戏音效

    Ruby's Adventrue游戏制作笔记(十四)Unity播放游戏音效 前言 一.创建背景音乐 二.创建新的脚本 三.在其他需要播放的所有脚本中进行操作 四.给敌人添加音效 五.给玩家添加移动音效 ...

  10. 深度学习入门笔记(十四):Softmax

    欢迎关注WX公众号:[程序员管小亮] 专栏--深度学习入门笔记 声明 1)该文章整理自网上的大牛和机器学习专家无私奉献的资料,具体引用的资料请看参考文献. 2)本文仅供学术交流,非商用.所以每一部分具 ...

最新文章

  1. 根据IP查找在交换机上的端口
  2. java内存区域之程序计数器
  3. JVM内存GC的骗局
  4. 21 款 IDEA 插件,yyds!
  5. 苹果成美国2021年最赚钱公司;用户已收到 HarmonyOS 2 正式版推送;Firefox 89.0 发布|极客头条...
  6. 1.1HashMap
  7. 【C++】-- STL容器适配器之stack
  8. 药方的量化方法笔记(学习与尝试):第二回 第一次 药方的拆解 量化方法的形式的发展 对药的量化分析
  9. file-saver实现文件流下载
  10. python中render是什么意思_Django中render_to_response和render的区别(转载)
  11. neatdm路径_网易有爱插件设置教程-网易有爱插件游戏路径如何设置
  12. CMD命令窗口全屏设置
  13. CodeForces 711C.Coloring Trees【DP】
  14. ecshop 2.7.2安装
  15. java 手机号归属地查询
  16. 计算机英语重点,计算机英语复习重点.doc
  17. 有技术含量的博客地址
  18. Rose2003汉化
  19. 磁盘RAID知识介绍
  20. kvm直通sata_「图」Proxmox VE下黑群晖硬盘休眠问题(不直通SATA控制器)[解决方案]_高清时代论坛...

热门文章

  1. Mybatis入门:2(xml形式的增删改查)
  2. 【spring】在不联网的情况下查看xml的定义规则的方法
  3. 腾讯云 短信服务 【学习记录 】
  4. oracle 分页写法
  5. MLeaksFinder简单实现原理
  6. python批量爬取文档
  7. bzoj 4025 二分图——线段树分治+LCT
  8. Redis配置到本地以及可视化工具的安装运用
  9. python_day16_pythom-mysql-API
  10. ⑨③-不能浪费拥有的年轻资本