《CLR Via C# 第3版》笔记之(十四) - 泛型高级
为了更好的利用泛型,现将泛型的一些高级特性总结一下。
主要内容:
- 泛型的协变和逆变
- 泛型的参数的约束
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版》笔记之(十四) - 泛型高级相关推荐
- 《深入浅出DPDK》读书笔记(十四):DPDK应用篇(DPDK与网络功能虚拟化:NFV、VNF、IVSHMEM、Virtual BRAS“商业案例”)
Table of Contents DPDK应用篇 DPDK与网络功能虚拟化 157.网络功能虚拟化 13.1.1起源 158.发展 159.OPNFV与DPDK NFV的部署 160.NFV的部署 ...
- 【CS231n】斯坦福大学李飞飞视觉识别课程笔记(十四):神经网络笔记2(上)
[CS231n]斯坦福大学李飞飞视觉识别课程笔记 由官方授权的CS231n课程笔记翻译知乎专栏--智能单元,比较详细地翻译了课程笔记,我这里就是参考和总结. [CS231n]斯坦福大学李飞飞视觉识别课 ...
- Windows保护模式学习笔记(十四)—— 阶段测试
Windows保护模式学习笔记(十四)-- 阶段测试 题目一 解题步骤 题目二 解题步骤 题目一 描述:给定一个线性地址,和长度,读取内容 int ReadMemory(OUT BYTE* buffe ...
- OpenCV学习笔记(十四):重映射:remap( )
OpenCV学习笔记(十四):重映射:remap( ) 图像的坐标映射是通过原图像与目标图像之间建立一种映射关系,这种映射关系有两种,一种是计算原图像任意像素在映射后图像的坐标位置,另一种是计算变换后 ...
- QT学习笔记(十四):QLayout的属性介绍
QT学习笔记(十四):QLayout的属性介绍 主要包括QBoxLayout.和QGridLayout以及QFormLayout等的参数类似. 我主要说明一下QGridLayout在QtDesigne ...
- MATLAB学习笔记(十四)
MATLAB学习笔记(十四) 一.线性方程组求解 1.1 直接法 1.1.1 利用左除运算符 1.1.2 利用矩阵分解 1.2 迭代法 1.2.1 雅可比(Jacobi)迭代法 1.2.2 高斯-赛德 ...
- python数据挖掘学习笔记】十四.Scipy调用curve_fit实现曲线拟合
#2018-03-28 10:02:08 March Wednesday the 13 week, the 087 day SZ SSMR python数据挖掘学习笔记]十四.Scipy调用curve ...
- 品优购项目笔记(十四):微信支付
品优购项目笔记(十四) 订单 订单三张表关系 提交订单 二维码 介绍 优势 容错级别 qrious二维码生成插件 微信支付 微信支付流程 项目支付流程 生成支付链接 查询是否支付成功 订单 订单三张表 ...
- Ruby‘s Adventrue游戏制作笔记(十四)Unity播放游戏音效
Ruby's Adventrue游戏制作笔记(十四)Unity播放游戏音效 前言 一.创建背景音乐 二.创建新的脚本 三.在其他需要播放的所有脚本中进行操作 四.给敌人添加音效 五.给玩家添加移动音效 ...
- 深度学习入门笔记(十四):Softmax
欢迎关注WX公众号:[程序员管小亮] 专栏--深度学习入门笔记 声明 1)该文章整理自网上的大牛和机器学习专家无私奉献的资料,具体引用的资料请看参考文献. 2)本文仅供学术交流,非商用.所以每一部分具 ...
最新文章
- 根据IP查找在交换机上的端口
- java内存区域之程序计数器
- JVM内存GC的骗局
- 21 款 IDEA 插件,yyds!
- 苹果成美国2021年最赚钱公司;用户已收到 HarmonyOS 2 正式版推送;Firefox 89.0 发布|极客头条...
- 1.1HashMap
- 【C++】-- STL容器适配器之stack
- 药方的量化方法笔记(学习与尝试):第二回 第一次 药方的拆解 量化方法的形式的发展 对药的量化分析
- file-saver实现文件流下载
- python中render是什么意思_Django中render_to_response和render的区别(转载)
- neatdm路径_网易有爱插件设置教程-网易有爱插件游戏路径如何设置
- CMD命令窗口全屏设置
- CodeForces 711C.Coloring Trees【DP】
- ecshop 2.7.2安装
- java 手机号归属地查询
- 计算机英语重点,计算机英语复习重点.doc
- 有技术含量的博客地址
- Rose2003汉化
- 磁盘RAID知识介绍
- kvm直通sata_「图」Proxmox VE下黑群晖硬盘休眠问题(不直通SATA控制器)[解决方案]_高清时代论坛...