转载

.NET可变性解析(协变和逆变)

 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 }

在上述代码中,我们定义了两个类,分别为 : projectcourse ,其中 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 }

在上述代码中,我们定义一个泛型接口 IBase<in T> , 参数类型为" in T " 说明它是可以逆变的,同时呢, Derived<T> 这个泛型类继承自 IBase<T> ,那么我们在实现的时候就可以这样来做。

IBase<Course> courseList = new Derived<Project>(); 在我们调用的这行代码中,将 Project 转换为了他的下级类 Course ,所以发生了逆变。

【三】泛型委托可变性

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

实例 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 也是一种,用来指定不变性,指定要求传入什么类型的就是什么类型,在一般我们开发过程中,通过都是通过这样的形式来传参的,比如:

实例 5 : 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 }

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

【五】总结

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

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

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

正文到此结束
Loading...