【一】何为可变性

可变性是.NET4.0中的一个新特性,可变性可分为 : 协变性、逆变性、不可变性.

那么在.NET4.0之前是否有可变性? 答案是肯定的,我们可以通过下面的几个实例来简单的了解一下.NET4.0之前的协变和逆变.

实例 1 : 方法参数的协变

static void Main(string[] args)
{
GetProject(new Course()); // Course 继承自 Project 此处进行了协变
}
static void GetProject(Project course)
{
Console.WriteLine(course.Name);
}

实例 2 : 数组协变以及执行时类型检查

 Course[] course = new Course[];
Project[] project = course;
project[] = new Excercise();

在上述代码中会抛出异常 "system.ArrayTypeMismatchException",因为从course转换为project会返回原始引用,所以course和project都是引用的同一个数组,对于数组而言,它是一个course数组,所以会拒绝存储对于非course类型的引用。数组的协变会导致类型安全性在执行时才能体现,而不能在编译时体现

可变性种类分类定义:

协变 : 说明泛型类型参数可以从一个派生类更改为它的基类,在C#中,是用out关键字标记协变量形式的泛型类型参数,协变量泛型类型参数只能出现在输出位置,比如作为方法的返回类型

逆变 : 说明泛型类型参数可以从一个基类更改为它的派生类,在C#中,是用in关键字标记逆变形式的泛型类型参数,逆变量泛型类型参数只能出现在输入位置,比如作为方法的参数。

不可变 :按引用类型传递变量,可以看成是ref参数,表示传入的类型必须与参数本身的类型完全一致,传入方法内部的值,将同样以相同的类型输出。

可变性是以一种类型安全的方式,将一个对象作为另一个对象来使用,在我们面向对象编程中,继承这一特性就很好的体现了对象的可变性.
任何使用了协变和逆变的转换都是引用转换,这意味着转换之后将返回相同的引用,它不会创建新的对象,只是认为现有引用与目标类型匹配,这与某个层次中,引用类型之间的转换是相同的。
在.NET4.0之前,泛型是不能够进行协变和逆变的,也就是说泛型的协变和逆变是C#4.0的一个新特性,泛型的协变和逆变也是为了保持类型的绝对安全性.
在泛型接口或者委托的声明中,.NET4.0能够使用out修饰符来指定类型参数的协变性,使用in修饰符来指定逆变性,声明完成之后,就可以对相关的类型进行隐式转换了,在接口和委托中,它们的工作方式是完全相同的.

【二】泛型接口可变性

我们使用的两个接口 : IEnumberable<T> (T 是协变的),原型为:IEnumberable<out T>  和 IComparer<T>(T 是逆变的),原型为 : IComparer<in T>,再次记忆提示 : 如果类型参数只用于输出,就使用out,如果只用于输入,就用in.
更多的泛型协变接口 : IEnumerable<T>、IEnumerator<T>、IQueryable<T> 和 IGrouping<TKey, TElement>
泛型逆变接口 : IComparer<T>、IComparable<T> 和 IEqualityComparer<T>
下面我们通过实例来演示一下接口的泛型可变性
实例 3 : 查看泛型接口集合IEnumberable<out T> 进行 协变
 class Project
{
public static void GetCourseByProjects(IEnumerable<Project> projects)
{
foreach (var p in projects)
{
Console.WriteLine(p);
}
}
public string Name { get; set; }
}
class Course : Project
{
public static void GetCourse()
{
List<Course> courseList = new List<Course>();
Project.GetCourseByProjects(courseList);
IEnumerable<Project> pList = courseList;
}
}

在上述代码中,我们定义了两个类,分别为 : projectcourse,其中course继承自project,在project中有一个方法 GetCourseByProject,这个方法有一个形参类型为 IEnumberable<Project>,(注意 : project是course的基类,IEnumberable是可以进行协变的,那么此处的实参我们可以传递任何继承自Project的类),在course中有一个方法GetCourse,这个方法用于通过course获取到这个course 所有的projectproject.GetCourseByProject(courseList);// 此处发生了协变,原本我们的GetCourseByProject的参数类型为IEnumberable<project>,在这里我们传递的是它的派生类List<Course>.同理在IEnbumerbale<project> pList = courseList 也发生了协变.

实例 4 : 定义泛型接口查看逆变

 static void Main(string[] args)
{
IBase<Course> getCourse = new Derived<Project>();
}
public class Derived<T> : IBase<T>
{ public string Name { get; set; } public void GetProject(T t)
{
Console.WriteLine("获取到项目");
}
}
public interface IBase<in T>
{
void GetProject(T t);
}
class Course : Project
{
public static void GetCourse()
{ }
}
在上述代码中,我们定义一个泛型接口 IBase<in T>, 参数类型为" in T " 说明它是可以逆变的,同时呢,Derived<T>这个泛型类继承自 IBase<T>,那么我们在实现的时候就可以这样来做。
IBase<Course> courseList = new Derived<Project>(); 在我们调用的这行代码中,将Project转换为了他的下级类Course,所以发生了逆变。

【三】泛型委托可变性

在我们看了,泛型接口的协变和逆变之后,对于泛型委托的可变性其实性质是一样的.我们可以通过下面两个实例来演示一下 :

实例 5 : 委托协变

 public delegate Project GetProject();

 static Course GetCourse()
{
return new Course();
}
GetProject projects = GetCourse;

在上述的代码中,我们首先定义了一个委托类型,getproject, 在GetProject projects = GetCourse,GetCourse是一个返回值为Course对象的一个函数, 此处发生了协变,Course类型转换为了Project类型,子类转换为父类.

实例 6 : 泛型委托协变

 public delegate T Find<out T>();
static void Main(string[] args)
{
Find<Course> getCourse = () => new Course(); // lambda
Find<Project> getProject = getCourse; // 发生了协变
}

在上述的代码中,我们定义了一个泛型委托,Find<out T>,这里指定out说明它可以进行协变,然后在Main函数中, 首先我们通过Lambda表达式声明了一个返回值为Course的方法,然后在将getCourse赋值给getProject,这里发生了协变.

实例 7 : 委托中的逆变

 public delegate void FindCourse(Course course);
static void GetProject(Project pro)
{
Console.WriteLine(pro.Name);
}
FindCourse getCourse = GetProject;

在上述的代码中,首先我们声明了一个带参数的委托FindCourse,参数类型为 Course , 然后注意在第六行代码中, 我们将 GetProject这个方法赋值给了 委托FindCourse,同时,GetProject这个方法的参数类型为 Project,ProjectCourse 的基类,所以在第六行代码中它发生了逆变.

实例 8 : 泛型委托中的逆变

 public delegate void Find<in T>(T t);
Find<Project> getProject = p => Console.Write("查看一个项目");
Find<Course> getCourse = getProject;

相信通过了前面的几个实例,这个例子也就不难看懂了,在上述的代码中,我们首先声明了一个泛型委托,并且泛型中有一个in说明是可以进行逆变,然后在第二行代码中,我们还是通过lambda表达式,创建一个参数类型为Project的函数,注意第三行代码, 第三行代码中将GetProject方法赋值给了getCourse,此处发生了逆变.

【四】.NET中可变性的好处

1 、更好的代码复用性.

通过刚才的几个实例,我们可以知道,如果在Project下还有Excerise,Test等派生类的话, 利用协变和逆变性,我们就可以直接 Project.GetCourseByProjects(ExceriseList); (协变了) . IBase<ExceriseList> exceriseList = new Dervied<Project>(); (逆变了)。所以我们就不需要在去繁多的创建多余的实例对象来调用Project和使用ExceriseList

2、更好的保持了泛型的类型安全性

首先,协变和逆变是通过out,in来指定的,编译器是不知道那种形式是协变那种形式是逆变的,通过out(输出参数)和in(输入参数),来指定参数的输入输出类型这一形式,很好的保持了泛型的类型安全性.

PS  : ref 也是一种,用来指定不变性,指定要求传入什么类型的就是什么类型,在一般我们开发过程中,通过都是通过这样的形式来传参的,比如:

实例 9 : ref双向传值,要求实参类型必须与形参类型完全一致

 Project p = new Project();
GetProject( ref p);
public static void GetProject(ref Project project)
{
Console.WriteLine(project.Name);
}

调用方法所传入的类型必须要与方法要求的参数类型完全一致

【五】总结

平日里我们觉得一些比较难的技术点,当我们花费一些时间去学习,去总结,去思考一下.会发现其实并不是我们想象中那么难, 难得是我们下定决心去做的那份意念而已.

通过本文我们了解到了协变性、逆变性、不变性的定义,以及它是通过一种什么样的形式来实现的, 另外通过实例我们也可以想到如果用好了它,也会给我的开发带来事半功倍的效果。使我们的代码更加优雅、提高程序可扩展性以及复用性,同时这不也是一种多态的体现吗?  【刘彬版权所有,如转载请注明出处.】

通过协变和逆变也有一些限制,这可能也是因为设计者出于类型安全性的方面考虑,它是不支持类的类型参数的可变性,只有接口和委托可以拥有可变的类型参数. 可变性只支持引用转换.

.NET解析系列目录

 

.NET可变性解析(协变和逆变)的更多相关文章

  1. c# 可变性解析(协变和逆变)

    之所以会想写关于协变和逆变的知识点,主要是因为在泛型委托中提到了这个知识点. 1.什么是可变性 可变性是.NET4.0中的一个特性,可变形分为:协变性,逆变性,不可变性. 2.在.NET4.0出来之前 ...

  2. 在net中json序列化与反序列化 面向对象六大原则 (第一篇) 一步一步带你了解linq to Object 10分钟浅谈泛型协变与逆变

    在net中json序列化与反序列化   准备好饮料,我们一起来玩玩JSON,什么是Json:一种数据表示形式,JSON:JavaScript Object Notation对象表示法 Json语法规则 ...

  3. C# 泛型的协变和逆变

    1. 可变性的类型:协变性和逆变性 可变性是以一种类型安全的方式,将一个对象当做另一个对象来使用.如果不能将一个类型替换为另一个类型,那么这个类型就称之为:不变量.协变和逆变是两个相互对立的概念: 如 ...

  4. 再谈对协变和逆变的理解(Updated)

    去年写过一篇博客谈了下我自己对协变和逆变的理解,现在回头看发现当时还是太过“肤浅”,根本没理解.不久前还写过一篇“黑”Java泛型的博客,猛一回头又是“肤浅”,今天学习Java泛型的时候又看到了协变和 ...

  5. c# 协变和逆变的理解

    1. 是什么 1.1 协变 协变指能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型.如 string 到 object 的转换.多见于类型参数用作方法的返回值. 1.2 逆变 逆变指能够 ...

  6. C# 泛型的协变和逆变 (转载)

    1. 可变性的类型:协变性和逆变性 可变性是以一种类型安全的方式,将一个对象当做另一个对象来使用.如果不能将一个类型替换为另一个类型,那么这个类型就称之为:不变量. 协变和逆变是两个相互对立的概念: ...

  7. C#-弄懂泛型和协变、逆变

    脑图概览 泛型声明和使用 协变和逆变 <C#权威指南>上在委托篇中这样定义: 协变:委托方法的返回值类型直接或者间接地继承自委托前面的返回值类型; 逆变:委托签名中的参数类型继承自委托方法 ...

  8. C#4.0泛型的协变,逆变深入剖析

    C#4.0中有一个新特性:协变与逆变.可能很多人在开发过程中不常用到,但是深入的了解他们,肯定是有好处的. 协变和逆变体现在泛型的接口和委托上面,也就是对泛型参数的声明,可以声明为协变,或者逆变.什么 ...

  9. C#协变和逆变

    我们知道在C#中,是可以将派生类的实例赋值给基类对象的.

随机推荐

  1. 20145311 实验一 "Java开发环境的熟悉"

    20145311 实验一 "Java开发环境的熟悉" 程序设计过程 实验内容 -实现四则运算,并进行测试 编写代码 1.四则运算就四种运算,我就做了个简单的,输入两个数,然后选择一 ...

  2. A8逻辑篇1.点亮一个LED(S5PV210.A8)

    一.虚拟机安装好后,我们用Fedora 双击.vmx文件,将会在虚拟机中打开 相应的生成: 这些文件 二.进入虚拟机页面 等待启动 账号选择其他 用户名:root 密码:111111 设置页面大小: ...

  3. SDN前瞻 软件定义网络的一些概念

    SDN的核心:可编程性 SDN的思想:SOA面向服务 面向服务的体系结构(service-oriented architecture SOA) 使网络连接的大量计算机易于合作,以 服务 而不是人工交互 ...

  4. 插入10W数据的两个程序比较

    程序1 添加10W数据 $count = 0; for ($i = 1;$i <= 100000 ;$i++) { $add_data = [ 'id' => $i, 'username' ...

  5. Android之微信开放平台实现分享(分享好友和朋友圈)

    开发中分享操作往往经常遇到,而且还是一些比较大型一定的平台,如微信,QQ,微博等.写这篇博客主要是把微信的的分享和相关操作表达一下,分享可以包含:文字,视频,音乐,图片等分享. 分享可以有 分享给好友 ...

  6. python批量给云主机配置安全组

    python批量给云主机配置安全组 用公有云的思路去思考去实现一个安全稳定.可伸缩和经济的业务构架,云运维是有别与传统运维的,比如说了解公有云的都知道安全组的概念,安全组跟防火墙功能很相似,那我的机器 ...

  7. js预解析相关知识总结以及一些好玩的面试题

    js预解析的题像在做智力题一样有意思~ 预解析 预解析:在解释这行代码之前发生的事情——变量的声明提前了,函数的声明提前 console.log(num) ——未定义Num,结果是报错 var num ...

  8. HDU 4687 Boke and Tsukkomi 一般图匹配,带花树,思路,输出注意空行 难度:4

    http://acm.hdu.edu.cn/showproblem.php?pid=4687 此题求哪些边在任何一般图极大匹配中都无用,对于任意一条边i,设i的两个端点分别为si,ti, 则任意一个极 ...

  9. iptables Configuration

    iptables usage: Add Rules: iptables -I INPUT -p tcp --dport -j ACCEPT iptables -I INPUT -p tcp --dpo ...

  10. linux 命令 --if

    if else-if else 语法格式: if condition1 then command1 elif condition2 then command2 else commandN fi 例如: ...