当前位置: 首页 > 编程日记 > 正文

《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

相关文章:

剑指offer-反转链表

输入一个链表&#xff0c;反转链表后&#xff0c;输出链表的所有元素。 #-*- coding:utf-8 -*- # class ListNode: # def __init__(self, x): # self.val x # self.next None class Solution:# 返回ListNodedef ReverseList(self, pHead):# write code h…

[NOIP模拟测试9]题(Problem) 题解 (组合数全家桶+dp)

达哥送分给我我都不要&#xff0c;感觉自己挺牛批。 $type0:$ 跟visit那题类似&#xff0c;枚举横向移动的步数直接推公式&#xff1a; $ans\sum C_n^i \times C_i^{\frac{i}{2}} \times C_{n-i}^{\frac{n-i}{2}},i\% 20$ $type1:$ 因为不能触碰负半轴&#xff0c;所以可以把右…

如何获取元素最终使用的css值

这个问题是在组件css加载方案中碰到的 场景&#xff1a; 元素的样式我们可以写在style里面&#xff0c;也可以写到外链css里面&#xff0c;如果没有设置&#xff0c;也会读取浏览器默认的css的&#xff0c;现在我们要计算getComputedstyle某个元素的最终所使用的css样式 区别&a…

【工具软件】webstorm如何使用快捷键生成固定代码

我们写前端页面时会有很多的机械重复代码&#xff0c;每写一个文件都要重新写一遍&#xff0c;枯燥且浪费时间&#xff0c;有没有一个方法可以快速生成这些代码呢&#xff1f;有&#xff01;webstorm替我们考虑到了这个问题&#xff0c;提供了解决方案&#xff0c;接下来让我们…

Citrix Receiver For Linux 预览版

Citrix各平台的客户端插件都在不断的完善和进行版本升级&#xff0c;但是For linux版本的ICA Client基本是一成不变&#xff0c;很长时间都不会有什么动静&#xff0c;但是Citrix产品都是搭配ThinClient来使用&#xff0c;而好多ThinClient都会使用Linux系统&#xff0c;而在Li…

5.20 单词小记

1.cart n. 二轮运货马车 vt. 用车装载 vi. 驾运货马车&#xff1b;用运货车运送 网络释义:购物车,小车,球车,分类与回归树 例:This would allow the server application to identify the cart from which it should add and removeitems. 这将使服务器应用程序能够确定它所应该…

windows性能计数器搜集方法

windows性能计数器搜集方法 PerfMon-Windows性能监视器是个好东西&#xff0c;可以辅助我们分析发生问题时间段服务器资源占用情况&#xff0c;但是部署性能计数器确实一个相当麻烦的事情&#xff0c;往往这种枯燥的事别人还做不了&#xff0c;只能由我们这些希望获取到PerfMon…

【HDU】3308 LCIS

1 #include<cstdio>2 #define MAXN 1000103 struct node4 {5 int left,right,val;6 };7 int a[MAXN];8 node tree[MAXN<<2];9 inline int MAX(int x,int y) 10 { 11 return x>y?x:y; 12 } 13 inline int MIN(int x,int y) 14 { 15 return x>y?…

【基础知识】如何在浏览器中查找元素属性节点

首先&#xff0c;什么是属性节点&#xff1f;属性节点是在编写html代码时&#xff0c;在html标签中添加的属性。 本文重点&#xff1a;如果您用的ie&#xff0c;我恳请您不要在用了&#xff0c;你们的每次使用都是打在前端工程师身上的一发子弹 代码实例&#xff1a; <!DOC…

C#系列五《多样化的程序分支》

上一篇介绍了C#程序设计分支的基本概念与基本操作&#xff0c;这篇的主题是高级的程序分支&#xff0c;例如&#xff1a;分支的嵌套与分支是使用的场景&#xff01;与三元运算符(&#xff1f;:),废话少说还是开始吧&#xff01; 1.三元运算符【&#xff1f;:】 三元运算符分为三…

Vue报错:Uncaught RangeError: Maximum call stack size exceeded

vue项目放在服务器上&#xff0c;报错如下&#xff1a; 堆栈溢出&#xff1a; 1、原因&#xff1a;有小类到大类的递归查询导致溢出 2、解决方法思想&#xff1a; A、在做递归查询时候由大类到小类去查询 B、匹配结果后及时return 退出&#xff0c;防止过多查询 转载于:https:/…

【Echarts】当页面宽度改变时如何使图表宽度自适应

问题&#xff1a; 当对echats不作任何处理时&#xff0c;图表宽度不会自适应&#xff0c;如图所示&#xff08;变绿的区域为echarts选中的div&#xff09;。 加载完后&#xff1a; 收缩浏览器页面之后&#xff1a; 解决方法&#xff1a; 编写js函数&#xff0c;当页面大小变…

【C#】登陆次数限制

我们在网上登陆的时候有些网站在用户多次输错密码之后会自动把账户冻结&#xff0c;不能在进行登陆&#xff0c;笔者这次做的winform程序就是要实现这种功能。 功能一&#xff1a;根据数据库字段判断用户名和密码是否匹配&#xff1b; 功能二&#xff1a;如果输入错误自动记录连…

linux 使用sendmail发送邮件

1111111111 转载于:https://www.cnblogs.com/We612/p/11274139.html

[转]MySQL innoDB数据插入性能优化

转载自: http://hiyijian.blog.163.com/blog/static/173086472201121621326471/ 起因&#xff1a;有一个innoDB引擎的表Table&#xff0c;在一个大概3000次的foreach循环中执行 INSERT INTO Table(columnA, columnB) VALUES (valueA, valueB)结果居然超出了60S的php执行限制&am…

vue+ivew-admin开发项目,内存占用过大解决办法

项目用的ivewadmin ivewUI&#xff0c;直接从github上拉下来用的&#xff0c;配置也没改&#xff0c;我们页面比较多&#xff0c;大该30个页面的样子&#xff0c;一启用&#xff0c;我们的电脑就卡了&#xff0c;然后&#xff0c;看一下&#xff0c;内存占用 1.5G了&#xff0c…

腾讯微视:向前一步是悲壮,向后一步是绝望zz

“换做以往的任何一场战争&#xff0c;微博之战、搜索之战和电商之战——大量投入但始终不见效果&#xff0c;打到现在腾讯肯定已经交牌了。微视的不同之处在于&#xff0c;它有些悲壮&#xff0c;因为这是腾讯最不想交出的一张牌。” 文 | 《财经》记者高洪浩 编辑|宋玮 如果有…

[C]线程中的数据

线程的私有存储空间包括&#xff1a; 栈&#xff08;一般情况下是私有的&#xff09;线程局部存储&#xff08;部分操作系统提供&#xff09;寄存器&#xff08;包括PC寄存器&#xff09; 从C程序员的角度来看&#xff1a; 线程私有&#xff1a; 局部变量函数的参数线程局部存储…

【css】如何使页面压缩时文本内容不换行

问题描述&#xff1a; 当页面变小时&#xff0c;有些情况下会出现下图所示的情况&#xff0c;即文本自动换行 页面变小前&#xff1a; 变小后: 解决方法&#xff1a; 使用CSS添加一个控制语句 .fence_control{white-space: nowrap; } 并在文本所在的标签属性中添加该控制 …

程序员面试题精选100题(61)-数对之差的最大值

题目&#xff1a;在数组中&#xff0c;数字减去它右边的数字得到一个数对之差。求所有数对之差的最大值。例如在数组{2, 4, 1, 16, 7, 5, 11, 9}中&#xff0c;数对之差的最大值是11&#xff0c;是16减去5的结果。 分治策略&#xff1a; 通常蛮力法不会是最好的解法&#xff0c…

Unity UGUI获取鼠标在屏幕的准确点击位置

想要获取鼠标在屏幕的准确点击位置&#xff0c;千万不要胡乱写&#xff0c;什么转化坐标系&#xff0c;什么Ray射线检测都是浮云。 1&#xff0c;转化坐标系只是相对而言&#xff0c;并不能准确实现当前鼠标点击在屏幕的位置&#xff1b; 2&#xff0c;Ray检测&#xff0c;hit是…

Numpy学习笔记(下篇)

目录 Numpy学习笔记&#xff08;下篇&#xff09;一、Numpy数组的合并与分割操作1、合并操作2、分割操作二、Numpy中的矩阵运算1、Universal Function2、矩阵运算3、向量和矩阵运算三、Numpy中的聚合操作四、Numpy中的arg运算1、索引操作2、排序和索引使用五、Fancy Indexing六…

VC串口通信编程-2

VC串口通信编程 (2009-07-08 13:48:40) 转载▼Win32串口编程&#xff08;转&#xff1a;韩耀旭&#xff09;  在工业控制中&#xff0c;工控机&#xff08;一般都基于Windows平台&#xff09;经常需要与智能仪表通过串口进行通信。串口通信方便易行&#xff0c;应用广泛。一般…

【bootstrap】如何解决页面缩小后上方导航栏格式变化的问题

问题描述&#xff1a; bootstrap官方模板中上方导航栏当缩小到一定程度后会发生格式的变化&#xff0c;但我想取消这种变化 页面缩小前&#xff1a; 页面缩小后&#xff1a; 解决方法&#xff1a; 经过阅读bootstrap的css代码后发现了这么一行代码&#xff1a; 将其删除后问…

ASIHttpRequest使用前的配置

ASIHttpRequest使用前的配置 1、从以下地址获取ASIHttpRequest相关资源文件&#xff1a; Github project page: http://github.com/pokeb/asi-http-request/tree Download the latest version: http://github.com/pokeb/asi-http-request/tarball…

当面试官问这些基础的Python问题时,竟然还有80%的人不会!

怎样才能开始一个交互式解释器的会话&#xff1f; 在Windows下可以通过点击开始按钮&#xff0c;选择“程序”&#xff0c;点击“Python”&#xff0c;然后选择“Python&#xff08;command line&#xff09;”菜单选项来开始一个交互会话。 你应该在哪里输入系统命令行来启动一…

xcode 4.3.2 use storyboard创建TableView

xcode 4.3.2 use storyboard 使用TableView&#xff0c;显示一个颜色列表&#xff0c;表格包括一张图片&#xff0c;一个文本&#xff0c;一行副文本。 TableView 呈现列表格式的数据&#xff0c;每一行是一个UITableViewCell对象&#xff0c;每个UITableViewCell可以显示文本标…

VScode 插件、配置记录

常用插件&#xff1a; Chinese&#xff08;Simplified&#xff09;pythonTabNineSettings Sync 配置&#xff1a; 外观设置为&#xff1a; Solarized Dark 转载于:https://www.cnblogs.com/yeran/p/11284772.html

【jqgrid】疑难杂症及解决方法(随缘更新)

最近在学习使用jqgrid&#xff0c;由于初次使用就要应用的工程的开发当中&#xff0c;还是遇到了不小的麻烦&#xff0c;在此梳理遇到的问题及解决方法。 遇到问题&#xff1a; 1、如何解决翻页键不好用的问题 2、如何调整grid的宽度和高度 3、当grid宽度超过页面的宽度之后…

vcenter converter 转换xenserver下linux的错误

需求&#xff1a;需将xenserver5.6上的一台ubuntu迁移到ESXi 4.1.0上面 首先部署一台vmware vcenter converter standalone,具体步骤省略&#xff0c;到99%时出现错误: 无法重新配置目标虚拟机。 检查日志,发现这么一句话&#xff1a; (converter.fault.CloneFault) { dynamicT…