.NET可变性解析(协变和逆变)
【一】何为可变性
可变性是.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 : 数组协变以及执行时类型检查
1 Course[] course = new Course[4]; 2 Project[] project = course; 3 project[0] = new Excercise();
在上述代码中会抛出异常 "system.ArrayTypeMismatchException",因为从course转换为project会返回原始引用,所以course和project都是引用的同一个数组,对于数组而言,它是一个course数组,所以会拒绝存储对于非course类型的引用。数组的协变会导致类型安全性在执行时才能体现,而不能在编译时体现
可变性种类分类定义:
协变 : 说明泛型类型参数可以从一个派生类更改为它的基类,在C#中,是用out关键字标记协变量形式的泛型类型参数,协变量泛型类型参数只能出现在输出位置,比如作为方法的返回类型
逆变 : 说明泛型类型参数可以从一个基类更改为它的派生类,在C#中,是用in关键字标记逆变形式的泛型类型参数,逆变量泛型类型参数只能出现在输入位置,比如作为方法的参数。
不可变 :按引用类型传递变量,可以看成是ref参数,表示传入的类型必须与参数本身的类型完全一致,传入方法内部的值,将同样以相同的类型输出。
【二】泛型接口可变性
1 class Project 2 { 3 public static void GetCourseByProjects(IEnumerable<Project> projects) 4 { 5 foreach (var p in projects) 6 { 7 Console.WriteLine(p); 8 } 9 } 10 public string Name { get; set; } 11 } 12 class Course : Project 13 { 14 public static void GetCourse() 15 { 16 List<Course> courseList = new List<Course>(); 17 Project.GetCourseByProjects(courseList); 18 IEnumerable<Project> pList = courseList; 19 } 20 }
在上述代码中,我们定义了两个类,分别为 : project和course,其中course继承自project,在project中有一个方法 GetCourseByProject,这个方法有一个形参类型为 IEnumberable<Project>,(注意 : project是course的基类,IEnumberable是可以进行协变的,那么此处的实参我们可以传递任何继承自Project的类),在course中有一个方法GetCourse,这个方法用于通过course获取到这个course 所有的project,project.GetCourseByProject(courseList);// 此处发生了协变,原本我们的GetCourseByProject的参数类型为IEnumberable<project>,在这里我们传递的是它的派生类List<Course>.同理在IEnbumerbale<project> pList = courseList 也发生了协变.
实例 4 : 定义泛型接口查看逆变
1 static void Main(string[] args) 2 { 3 IBase<Course> getCourse = new Derived<Project>(); 4 } 5 public class Derived<T> : IBase<T> 6 { 7 8 public string Name { get; set; } 9 10 public void GetProject(T t) 11 { 12 Console.WriteLine("获取到项目"); 13 } 14 } 15 public interface IBase<in T> 16 { 17 void GetProject(T t); 18 } 19 class Course : Project 20 { 21 public static void GetCourse() 22 { 23 24 } 25 }
【三】泛型委托可变性
在我们看了,泛型接口的协变和逆变之后,对于泛型委托的可变性其实性质是一样的.我们可以通过下面两个实例来演示一下 :
实例 5 : 委托协变
1 public delegate Project GetProject(); 2 3 static Course GetCourse() 4 { 5 return new Course(); 6 } 7 GetProject projects = GetCourse;
在上述的代码中,我们首先定义了一个委托类型,getproject, 在GetProject projects = GetCourse,GetCourse是一个返回值为Course对象的一个函数, 此处发生了协变,Course类型转换为了Project类型,子类转换为父类.
实例 6 : 泛型委托协变
1 public delegate T Find<out T>(); 2 static void Main(string[] args) 3 { 4 Find<Course> getCourse = () => new Course(); // lambda 5 Find<Project> getProject = getCourse; // 发生了协变 6 }
在上述的代码中,我们定义了一个泛型委托,Find<out T>,这里指定out说明它可以进行协变,然后在Main函数中, 首先我们通过Lambda表达式声明了一个返回值为Course的方法,然后在将getCourse赋值给getProject,这里发生了协变.
实例 7 : 委托中的逆变
1 public delegate void FindCourse(Course course); 2 static void GetProject(Project pro) 3 { 4 Console.WriteLine(pro.Name); 5 } 6 FindCourse getCourse = GetProject;
在上述的代码中,首先我们声明了一个带参数的委托FindCourse,参数类型为 Course , 然后注意在第六行代码中, 我们将 GetProject这个方法赋值给了 委托FindCourse,同时,GetProject这个方法的参数类型为 Project,Project为Course 的基类,所以在第六行代码中它发生了逆变.
实例 8 : 泛型委托中的逆变
1 public delegate void Find<in T>(T t); 2 Find<Project> getProject = p => Console.Write("查看一个项目"); 3 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双向传值,要求实参类型必须与形参类型完全一致
1 Project p = new Project(); 2 GetProject( ref p); 3 public static void GetProject(ref Project project) 4 { 5 Console.WriteLine(project.Name); 6 }
调用方法所传入的类型必须要与方法要求的参数类型完全一致
【五】总结
平日里我们觉得一些比较难的技术点,当我们花费一些时间去学习,去总结,去思考一下.会发现其实并不是我们想象中那么难, 难得是我们下定决心去做的那份意念而已.
通过本文我们了解到了协变性、逆变性、不变性的定义,以及它是通过一种什么样的形式来实现的, 另外通过实例我们也可以想到如果用好了它,也会给我的开发带来事半功倍的效果。使我们的代码更加优雅、提高程序可扩展性以及复用性,同时这不也是一种多态的体现吗? 【刘彬版权所有,如转载请注明出处.】
通过协变和逆变也有一些限制,这可能也是因为设计者出于类型安全性的方面考虑,它是不支持类的类型参数的可变性,只有接口和委托可以拥有可变的类型参数. 可变性只支持引用转换.
.NET解析系列目录
转载于:https://www.cnblogs.com/DeepLearing/p/4592759.html
.NET可变性解析(协变和逆变)相关推荐
- c#: 协变和逆变深度解析
环境: window 10 .netcore 3.1 vs2019 16.5.1 一.为什么要有协变? 首先看下面的代码: 还有下面的: 其实上面报错的是同一个问题,就是你无法用List<Fru ...
- Kotlin的型变解析(协变、逆变和不变)
一.首先来看一个例子 import java.util.*/*** @author:wangdong* @description:型变*/fun main(args: Array<String& ...
- 泛型--协变与逆变(转)
对于泛型的知识,一直比较模糊,现在有机会整理一下,突发发现C#还有很多你不知道的东东,继续.NET FrameWork中泛型的协变与逆变: 1. 可变性的类型:协变性和逆变性 可变性是以一种类型安全的 ...
- 这一次,终于弄懂了协变和逆变
一.前言 刘大胖决定向他的师傅灯笼法师请教什么是协变和逆变. 刘大胖:师傅,最近我在学习泛型接口的时候看到了协变和逆变,翻了很多资料,可还是不能完全弄懂. 灯笼法师:阿胖,你不要被这些概念弄混,编译器 ...
- C# 泛型的协变和逆变
1. 可变性的类型:协变性和逆变性 可变性是以一种类型安全的方式,将一个对象当做另一个对象来使用.如果不能将一个类型替换为另一个类型,那么这个类型就称之为:不变量.协变和逆变是两个相互对立的概念: 如 ...
- C# 4.0中的协变和逆变(一)
在刚刚落下帷幕的PDC上,我们得到了很多振奋的消息,包括C# 4.0及VS2010等等.Anders Liu 已经 将C# 4.0 新特性白皮书翻译了 出来,那里面有非常详细的介绍. C#的发展是很快 ...
- 深入理解 C# 协变和逆变【转】
msdn 解释如下: "协变"是指能够使用与原始指定的派生类型相比,派生程度更大的类型. "逆变"则是指能够使用派生程度更小的类型. 解释的很正确,大致就是这样 ...
- C#协变和逆变 - 译
https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/covariance-contravariance/ ...
- C#中协变和逆变的基本概念、List和List.Select方法的命名空间
在 C# 中,协变和逆变能够实现数组类型.委托类型和泛型类型参数的隐式引用转换. 协变保留分配兼容性,逆变则与之相反. msdn 解释如下: "协变"是指能够使用与原始指定的派生类 ...
最新文章
- 扎克伯格凌晨放大招,说几句话能造世界的那种
- Java多线程(三):使用ThreadPoolExecutor创建线程池
- xshell 安装yum_xshell上安装docker
- MATLAB观察日志(part1)--求极限
- 【BZOJ4289】Tax,堆优化dijsktra的最短路问题
- 比特大爆炸为啥老显示服务器满,《有可能是史上最长停服维护公告》
- [HDU4585]Shaolin
- 神经网络自适应反馈控制设计
- 设置word中第一页不显示页码,第二页页码从1开始
- win7或win2008系统中,出现【已停止工作,联机检查解决方案并关闭该程序,关闭程序】解决方法!
- 微信公众平台开发之签到积分查询功能
- Linux中断子系统---中断申请request_irq()与中断线程化request_threaded_irq()
- 将视频背景扣掉换成白色相关知识
- 2016年3月15日Android实习日记
- win7的IE图标不见了 怎么找回?
- 佳明245服务器维修,吐槽下佳明245M手表
- python网络爬虫应用_python网络爬虫应用实战
- 【时间序列 - 02】ExponentialSmoothing - 指数平滑算法
- 为了用最小的箱子装最多的汽水,数学家们研究到了 24 维!
- 计算机为什么学电路图,怎么才能看懂电路图
热门文章
- Activity中与ListActivity中使用listview区别
- CSS 外补白(Margin) 内补白(Padding) 边框属性 定位(positioning)属性 布局(layout)属性
- gridView里如何添加详情按钮,点击它可以转到另一页,以获取该行的详细信息。
- 数据结构 之 并查集
- golang中的collection
- golang中的byte与rune
- 吉林大学数据结构(C++版)
- a为数组名。sizeof(a)和sizeof(a)有什么区别?结果是?
- C九:free与malloc
- HTML一些常用的标签