可变性是.NET4.0中的一个新特性,可变性可分为 : 协变性、逆变性、不可变性.编程
那么在.NET4.0以前是否有可变性? 答案是确定的,咱们能够经过下面的几个实例来简单的了解一下.NET4.0以前的协变和逆变.c#
实例 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[4]; Project[] project = course; project[0] = new Excercise();
在上述代码中会抛出异常 "system.ArrayTypeMismatchException",由于从course转换为project会返回原始引用,因此course和project都是引用的同一个数组,对于数组而言,它是一个course数组,因此会拒绝存储对于非course类型的引用。数组的协变会致使类型安全性在执行时才能体现,而不能在编译时体现 ide
可变性种类分类定义:函数
协变 : 说明泛型类型参数能够从一个派生类更改成它的基类,在C#中,是用out关键字标记协变量形式的泛型类型参数,协变量泛型类型参数只能出如今输出位置,好比做为方法的返回类型学习
逆变 : 说明泛型类型参数能够从一个基类更改成它的派生类,在C#中,是用in关键字标记逆变形式的泛型类型参数,逆变量泛型类型参数只能出如今输入位置,好比做为方法的参数。spa
不可变 :按引用类型传递变量,能够当作是ref参数,表示传入的类型必须与参数自己的类型彻底一致,传入方法内部的值,将一样以相同的类型输出。设计
可变性是以一种类型安全的方式,将一个对象做为另外一个对象来使用,在咱们面向对象编程中,继承这一特性就很好的体现了对象的可变性.orm
任何使用了协变和逆变的转换都是引用转换,这意味着转换以后将返回相同的引用,它不会建立新的对象,只是认为现有引用与目标类型匹配,这与某个层次中,引用类型之间的转换是相同的。
在.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; } }
在上述代码中,咱们定义了两个类,分别为 : 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 : 定义泛型接口查看逆变
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,Project为Course 的基类,因此在第六行代码中它发生了逆变.
实例 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
二、更好的保持了泛型的类型安全性
首先,协变和逆变是经过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解析这一系列的文章....
做者:刘彬
出处:http://albin.blog.51cto.com/
本文版权归做者和51CTO共有,欢迎转载,但未经做者赞成必须保留此段声明,且在文章页面明显位置给出原文链接,不然保留追究法律责任的权利。