《C#高级编程》学习笔记------抗变和协变
1.协变和抗变
在.NET 4之前,泛型接口是不变的。.NET 4通过协变和抗变为泛型接口和泛型委托添加了一个重要的扩展。协变和抗变指对参数和返回值的类型进行转换。例如,可以给一个需要Shape参数的方法传送Rectangle参数码?下面用示例说明这些扩展的优点。
在.NET中,参数类型是协变的。假定有Shape和Rectangle类,Rectangle派生自Shape基类。声明Display()方法是为了接受Shape类型的对象作为其参数:
public void Display(Shape o) { }
现在可以传递派生自Shape基类的任意对象。因为Rectangle派生自Shape,所以Rectangle满足Shape的所有要求,编译器接受这个方法调用:
Rectangle r = new Rectangle { Width= , Height=2.5};
Display(r);
方法的返回类型是抗变的。当方法返回一个Shape时,不能把它赋予Rectangle,因为Shape不一定总是Rectangle。反过来是可行的:如果一个方法像GetRectangle()方法那样返回一个Rectangle,
public Rectangle GetRectangle();
就可以把结果赋予某个Shape:
Shape s = GetRectangle();
在.NET Framework 4版本之前,这种行为方式不适用于泛型。在C# 4中,扩展后的语言支持泛型接口和泛型委托的协变和抗变。下面开始定义Shape基类和Rectangle类:
public class Shape
{
public double Width { get; set; }
public double Height { get; set; }
public override string ToString()
{
return String.Format("Width: {0}, Height: {1}", Width, Height);
}
}
public class Rectangle: Shape
{
}
2.泛型接口的协变
如果泛型类型用out关键字标注,泛型接口就是协变的。这也意味着返回类型只能是T。接口IIndex与类型T是协变的,并从一个只读索引器中返回这个类型:
public interface IIndex<out T>
{
T this[int index] { get; }
int Count { get; }
}
代码段Variance/IIndex.cs
如果对接口IIndex使用了只读索引器,就把泛型类型T传递给方法,并从方法中检索这个类型。这不能通过协变来实现-- 泛型类型必须定义为不变的。不使用out和in标注,就可以把类型定义为不变的。
IIndex<T>接口用RectangleCollection类来实现。RectangleCollection类为泛型类型T定义了Rectangle:
public class RectangleCollection: IIndex<Rectangle>
{
private Rectangle[] data = new Rectangle[]
{
new Rectangle { Height=, Width=},
new Rectangle { Height=, Width=},
new Rectangle { Height=4.5, Width=2.9}
};
public static RectangleCollection GetRectangles()
{
return new RectangleCollection();
}
public Rectangle this[int index]
{
get
{
if (index < || index > data.Length)
throw new ArgumentOutOfRangeException("index");
return data[index];
}
}
public int Count
{
get
{
return data.Length;
}
}
}
代码段Variance/RectangleCollection.cs
RectangleCollection.GetRectangle()方法返回一个实现IIndex<Rectangle>接口的RectangleCollection类,所以可以把返回值赋予IIndex<Rectangle>类型的变量rectangle。因为接口是协变的,所以也可以把返回值赋予IIndex<Shape>类型的变量。Shape不需要Rectangle没有提供的内容。使用shapes变量,就可以在for循环中使用接口中的索引器和Count属性:
static void Main()
{
IIndex<Rectangle> rectangles = RectangleCollection.GetRectangles();
IIndex<Shape> shapes = rectangles;
for (int i = ; i < shapes.Count; i++)
{
Console.WriteLine(shapes[i]);
}
}
代码段Variance/Program.cs
3.泛型接口的抗变
如果泛型类型用in关键字标注,泛型接口就是抗变的。这样,接口只能把泛型类型T用作其方法的输入:
public interface IDisplay<in T>
{
void Show(T item);
}
代码段Variance/IDisplay.cs
ShapeDisplay类实现IDisplay<Shape>,并使用Shape对象作为输入参数:
public class ShapeDisplay: IDisplay<Shape>
{
public void Show(Shape s)
{
Console.WriteLine("{0} Width: {1}, Height: {2}", s.GetType().Name,
s.Width, s.Height);
}
}
代码段Variance/ShapeDisplay.cs
创建ShapeDisplay的一个新实例,会返回IDisplay<Shape>,并把它赋予shapeDisplay变量。因为IDisplay<T>是抗变的,所以可以把结果赋予IDisplay<Rectangle>,其中Rectangle派生自Shape。这次接口的方法只能把泛型类型定义为输入,而Rectangle满足Shape的所有要求:
static void Main()
{
//...
IDisplay<Shape> shapeDisplay = new ShapeDisplay();
IDisplay<Rectangle> rectangleDisplay = shapeDisplay;
rectangleDisplay.Show(rectangles[]);
}
代码段Variance/Program.cs
《C#高级编程》学习笔记------抗变和协变的更多相关文章
- Asp.net MVC4高级编程学习笔记-视图学习第一课20171009
首先解释下:本文只是对Asp.net MVC4高级编程这本书学习记录的学习笔记,书本内容感觉挺简单的,但学习容易忘记,因此在边看的同时边作下了笔记,可能其它朋友看的话没有情境和逻辑顺序还请谅解! 一. ...
- Unix环境高级编程学习笔记——fcntl
写这篇文正主要是为了介绍下fcntl,并将我自己在学习过程中的一些理解写下来,不一定那么官方,也有错误,希望指正,共同进步- fcntl: 一个修改一打开文件的性质的函数.基本的格式是 int fcn ...
- Javascript高级编程学习笔记(3)—— JS中的数据类型(1)
前一段时间由于事情比较多,所以笔记耽搁了一段时间,从这一篇开始我会尽快写完这个系列. 文章中有什么不足之处,还望各位大佬指出. JS中的数据类型 上一篇中我写了有关JS引入的Script标签相关的东西 ...
- Unix环境高级编程学习笔记——dup
dup 和 dup2 dup和dup2,都是用来将一个文件描述符复制给另一个文件描述符上,这两个文件描述符都指向同一个文件状态标志上. 只是文件描述符的大小不一样,dup所执行下的复制,肯定是返回 ...
- Asp.net MVC4高级编程学习笔记-视图学习第三课Razor页面布局20171010
Razor页面布局 1) 在布局模板页中使用@RenderBody标记来渲染主要内容.比如很多web页面说头部和尾部相同,中间内容部分使用@RenderBody来显示不同的页面内容. 2) 在布局 ...
- Asp.net MVC4高级编程学习笔记-模型学习第四课基架与模型绑定20171027
MVC模型 一.构建基架. MVC中的基架可以为应用程序提供CURD各种功能生成所需要的样板代码.在添加控制器的时候可以选择相应的模板以及实体对象来生成相应的模板代码. 首先定义一个模型类如下所示: ...
- Javascript高级编程学习笔记(97)—— WebGL(3) WebGL上下文(1)
WebGL上下文 在支持WebGL的浏览器中,WebGL的名字为 "experimental-webgl",这是由于 webgl 的规范仍未制定完成 制定完成后名字就会改为简单的 ...
- Javascript高级编程学习笔记(57)—— 事件(1)事件流
事件 JS与HTML的交互是通过事件实现的 而事件指的就是:文档或浏览器窗口特定的交互瞬间 可以通过侦听器来预定事件,以便在事件发生时执行相应的代码 这种模式也是设计模式中的观察者模式 事件流 有了事 ...
- Javascript高级编程学习笔记(53)—— DOM2和DOM3(5)遍历
遍历 “DOM2级遍历和范围” 定义了两个用于辅助完成顺序遍历的DOM结构类型 NodeIterator 和 TreeWalk 上述两种类型可以基于给定起点的DOM结构执行深度优先的遍历操作 对于检测 ...
随机推荐
- buildroot 搭建ftpd 服务器记录
vsftpd 搭建失败,应该是buildroot 文件系统还有操作没有理解透,还需要不断的学习. 所以用轻量级的 ftpd 进行替代, 步骤如下: // ---> make busybox-me ...
- spingboot集成jpa(二)
一.使用单元测试 单元测试在每个项目环境中必不可少,springboot中如何使用单元测试 在src/test/java中新建测试类DemoApplicationTest.java 项目结构: De ...
- IE6、IE7、IE8、Firefox兼容性
整理关于IE6.IE7.IE8.Firefox兼容性CSS HACK问题 1.区别IE和非IE浏览器CSS HACK代码 #divcss5{background:blue; /*非IE 背景藍色*/b ...
- iOS边练边学--图片的拉伸
图片拉伸方法一: IOS 5.0以后才有的方法: - (void)viewDidLoad { [super viewDidLoad]; UIImageView *imageView = [[UIIma ...
- .net垃圾回收
垃圾回收器(gc)用来在.NET中进行内存管理,特别是它可以恢复正在运行的应用程序需要的内存. .NET运行库采用的方法是垃圾回收器,这是一个程序,其目的是清理内存.方法是所有动态请求的内存都分配到堆 ...
- 在使用R做数据挖掘时,最常用的数据结构莫过于dataframe了,下面列出几种常见的dataframe的操作方法
原网址 http://blog.sina.com.cn/s/blog_6bb07f83010152z0.html 在使用R做数据挖掘时,最常用的数据结构莫过于dataframe了,下面列出几种常见的d ...
- 关于Unity的入门游戏飞机大战的开发(上)
每个组件都是一个类的实例,要获得某个组件,要先创建一个同类型的组件类实例,然后把实例传引用过去,就可以对想要的组件实例进行操作. 做游戏一般创建一个逻辑节点,里面只管逻辑,再创建一个动画节点,里面有好 ...
- 关于Cocos2d-x中的scheduleUpdate和update方法的使用
一.如果要让某类实例对象要连续执行某些语句(比如让每个Block实例从运行框最右边移动到最左边) 要在Block类中增加一些东西 1.先在其.cpp文件的init()函数中执行scheduleUpda ...
- pip -i 和 -U 参数
例子: pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -U funcat -i: 指定库的安装源 -U:升级 原来已经安装的包,不带U ...
- e585. Converting Between RGB and HSB Colors
This example demonstrates how to convert between a color value in RGB (three integer values in the r ...