该文章中使用了较多的 委托delegate和Lambda表达式,如果你并不熟悉这些,请查看我的文章《委托与匿名委托》、《匿名委托与Lambda表达式》以便帮你建立完整的知识体系。

在C#从诞生到发展壮大的过程中,新知识点不断引入。逆变与协变并不是C#独创的,属于后续引入。在Java中同样存在逆变与协变,后续我还会写一篇Java逆变协变的文章,有兴趣的朋友可以关注一下。

逆变与协变,听起来很抽象、高深,其实很简单。看下面的代码:

class Person
{ }
class Student : Person
{ }
class Teacher: Person
{ } class Program
{
static void Main(string[] args)
{
List<Person> plist = new List<Person>();
plist = new List<Student>();
plist = new List<Teacher>();
}
}

在上面的代码中,plist = new List<Student>()、plist = new List<Teacher>()两句产生编译错误。虽然Person是Student/Teacher的父类,但List<Person>类型却不是List<Student/Teacher>类型的父类,所以上面的赋值语句报类型转换失败错误。

如上这样的赋值操作,在C# 4.0之前是不允许的,至于为什么不允许,类型安全是首要因素。看下面的示例代码:

List<Person> plist = new List<Student>();
plist.Add(new Person());
plist.Add(new Student());
plist.Add(new Teacher());

如下示例,假设 List<Person> plist = new List<Student>() 允许赋值,那plist虽然类型为List<Person>集合,但实际指向确是List<Student>集合。plist.Add(new Person()),添加操作实际调用的是List<Student>.Add()。Person类型无法安全转换为Student,所以这样的集合定义没有意义,所以上面的假设不成立。

但情况在C# 4.0之后发生了变化,并不是"不可能发生的事情发生了",而是应用的灵活性做出了新的调整。同样的在C# 4.0中上面的程序仍是不被允许的,但却出现了例外。从C# 4.0开始,在泛型委托、泛型接口中,允许特殊情况的发生(实质上并未发生特殊变化,后面说明)。如下示例:

delegate void Work<T>(T item);

class Person
{
public string Name { get; set; }
}
class Student : Person
{
public string Like { get; set; }
}
class Teacher : Person
{
public string Teach { get; set; }
} class Program
{
static void Main(string[] args)
{
Work<Person> worker = (p) => { Console.WriteLine(p.Name); }; ;
Work<Student> student_worker = (s) => { Console.WriteLine(s.Like); };
student_worker = worker; //此处编译错误
}
}

根据前面的理论支持,student_worker = worker;的错误很容易理解。但此处我们程序的目的是让 woker  充当 Work<Student> 的功能,以后调用 student_worker(s)实际调用的是woker(s)。为了满足我们的需求,需要程序做2方面的处理:

1、因在调用student_worker(s)时,实质执行的是woker(s),所以需要s变量的类型能成功转换为woker需要的参数类型。

2、需要告诉编译器,此处允许将 Work<Person> 类型的对象赋值给 Work<Student>类型的变量。

条件1在调用时student_worker(),时编译器会提示要求参数必须是Student类型对象,该对象可成功转换为Person类型对象。

条件2则需要对Woke委托定义进行调整,调整如下:

delegate void WorkIn<in T>(T item);

委托名字改为WorkIn是为却别修改前后的委托,关键之处为<in T>。通过增加 in 关键字,标注该泛型委托的类型参数T,仅作为委托方法的参数来使用。此时上面的程序便可成功编译并执行。

delegate void WorkIn<in T>(T item);
class Program
{
static void Main(string[] args)
{
WorkIn<Person> woker = (p) => { Console.WriteLine(p.Name); };
WorkIn<Student> student_worker = woker;
student_worker(new Student() { Name="tom", Like="C#" }); }
}

对于要求类型参数为子类型,允许赋值类型参数为父类型值的这种情况,称为逆变。逆变在C#中需要用 in 标注泛型的类型参数。逆变虽叫逆变,但只是形式上看似父类对象赋值给子类变量,实质上是方法调用时参数的类型转换。Student s = new Person(),这是不可能的,这不是逆变是错误。

上面的代码如你能转换为下面的形式,那你就可以忘却逆变,本质比现象更重要

C# 逆变与协变的更多相关文章

  1. Java中的逆变与协变

    看下面一段代码 Number num = new Integer(1); ArrayList<Number> list = new ArrayList<Integer>(); ...

  2. scala 学习: 逆变和协变

    scala 逆变和协变的概念网上有很多解释, 总结一句话就是 参数是逆变的或者不变的,返回值是协变的或者不变的. 但是为什么是这样的? 协变: 当s 是A的子类, 那么func(s) 是func(A) ...

  3. Scala 深入浅出实战经典 第81讲:Scala中List的构造是的类型约束逆变、协变、下界详解

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-97讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...

  4. C#4.0新特性(3):变性 Variance(逆变与协变)

    一句话总结:协变让一个粗粒度接口(或委托)可以接收一个更加具体的接口(或委托)作为参数(或返回值):逆变让一个接口(或委托)的参数类型(或返回值)类型更加具体化,也就是参数类型更强,更明确. 通常,协 ...

  5. Java中的逆变与协变(转)

    看下面一段代码 Number num = new Integer(1); ArrayList<Number> list = new ArrayList<Integer>(); ...

  6. Java 逆变与协变的名词说明

    最近在研究Thinking in Java的时候,感觉逆变与协变有点绕,特意整理一下,方便后人.我参考于Java中的逆变与协变,但是该作者整理的稍微有点过于概念化,我在这里简单的说一下 我对于协变于逆 ...

  7. Java 逆变与协变

    最近一直忙于学习模电.数电,搞得头晕脑胀,难得今天晚上挤出一些时间来分析一下Java中的逆变.协变.Java早于C#引入逆变.协变,两者在与C#稍有不同,Java中的逆变.协变引入早于C#,故在形式没 ...

  8. .NET 4.0中的泛型逆变和协变

    转载自:http://www.cnblogs.com/Ninputer/archive/2008/11/22/generic_covariant.html:自己加了一些理解 随Visual Studi ...

  9. .NET:为什么需要逆变和协变

    为啥需要协变和逆变? 我目前想到的理由是:逆变和协变的目的是支持多态. 一个小例子 不明白为啥输出的是false和true. using System; using System.Collection ...

随机推荐

  1. 中国(北方)大学生程序设计训练赛(第二周) (A B D G)

    比赛链接 A题是KMP,先把A拼接到B的后面,然后利用next数组的意义(包括其具体含义,以及失配时的应用),得到ans #include<bits/stdc++.h> using nam ...

  2. HDU - 4995 - Revenge of kNN

    题目链接 : https://vjudge.net/problem/HDU-4995 题目大意  :   读入n个点的坐标与该点所拥有的值val,进行m次查询,对于每一次查询,读入该点的坐标,计算离该 ...

  3. 关于javacc的认识

    http://www.cnblogs.com/Gavin_Liu/archive/2009/03/07/1405029.html

  4. ORA-12516: TNS: 监听程序找不到符合协议堆栈要求的可用处理程”的异常

    简单说明:我们开发时多人开发,会频繁访问服务器数据库,结果当连接数大的时候,就会报ora-12516的错误,ORA-12516: TNS: 监听程序找不到符合协议堆栈要求的可用处理程"的异常 ...

  5. SpringBoot上传任意文件功能的实现

    一.pom文件依赖的添加 <dependencies> <dependency> <groupId>org.springframework.boot</gro ...

  6. Python 导入模块

    导入模块 方法1:import  模块名                      //导入整个模块,调用方法时,需要加上模块名 方法2:from  模块名  import  方法           ...

  7. Spring+SpringMVC+MyBatis+easyUI整合进阶篇(一)设计一套好的RESTful API

    写在前面的话 看了一下博客目录,距离上次更新这个系列的博文已经有两个多月,并不是因为不想继续写博客,由于中间这段时间更新了几篇其他系列的文章就暂时停止了,如今已经讲述的差不多,也就继续抽时间更新< ...

  8. TensorFlow学习笔记1——安装

    1. 准备好Anaconda环境 具体参见:http://blog.csdn.net/zhdgk19871218/article/details/46502637 2. 建立名叫TensorFlow的 ...

  9. 关于dfs+剪枝第一篇:hdu1010

    最近进入了dfs关于剪枝方面的学习,遇到的第一道题就是hdu的1010.一道很基础的剪枝..可我不幸地wa了很多次(待会再解释wa的原因吧QAQ),首先我们来看一下题目. Problem Descri ...

  10. hibernate exception nested transactions not supported 解决方法

    开启事务之前先判断事务是否已经打开,方法如下: JdbcTransaction tx=(JdbcTransaction) session.beginTransaction(); 改为JdbcTrans ...