在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
● 协变和逆变的定义是什么?给我们带来了什么便利?如何应用? 前言协变和逆变是c#4.0引入的新概念,主要是针对于泛型而言的。有了它们,我们可以更准确的定义泛型委托和接口。 首先举个栗子,比如IEnumerable<T> 接口是协变的,我们实现了一个这样的函数: static void PrintPersonName(IEnumerable<Person> persons) { foreach (Person person in persons) { Console.WriteLine(person.Name); } } 那么PrintPersonName这个方法就可以接受任何Person类的子类型列表作为它的参数。比如,若Student是Person的子类,那么可以这样调用: IList<Student> students = new List<Student>(); PrintPersonName(students); 在C#4.0之前,上面的语句是无法通过编译的,因为IEnumerable接口是不可变(invariant)的。PrintPersonName方法只能接受Person列表作为其参数。如果Person的子类想实现同样的功能就必须自己PrintName方法,或者将PrintPersonName方法定义为泛型方法: static void PrintPersonName<T>(IEnumerable<T> persons) where T : Person { foreach (Person person in persons) { Console.WriteLine(person.Name); } } 上述方法可以运行的很好,但是不如直接协变接口这样简单明了。 协变和逆变的定义1、不可变如果一个接口的泛型参数没有in或out修饰符,它就是不可变的。比如IList<T>。我们既不能这样: IList<Person> personList1 = null; IList<Student> stuList = null; personList1 = stuList; // 编译错误:无法将IList<Student>隐式转换为IList<Person> 也不能这样: IList<Person> personList1 = null; IList<Student> stuList = null; stuList = personList1; // 编译错误:无法将IList<Person>隐式转换为IList<Student> 只能这样: IList<Person> personList1 = null; IList<Person> personList2 = null; personList1 = personList2; 2、协变如果一个接口的泛型参数有out修饰符,它就是协变的。比如IEnumerable<out T>。我们既可以这样: IEnumerable<Person> persons1 = null; IEnumerable<Person> persons2 = null; persons1 = persons2; 也可以这样: IEnumerable<Person> persons = null; IEnumerable<Student> students = null; persons = students; // 可以将IEnumerable<Student>隐式转换为IEnumerable<Person> 但不能这样: IEnumerable<Person> persons = null; IEnumerable<Student> students = null; students = persons; // 无法将IList<Person>隐式转换为IList<Student> 3、逆变如果一个接口的泛型参数有in修饰符,它就是逆变的。比如IComparer<in T>。我们既可以这样: IComparer<Person> personComparer1 = null; IComparer<Person> personComparer2 = null; personComparer1 = personComparer2; 也可以这样: IComparer<Person> personComparer = null; IComparer<Student> studentComparer = null; studentComparer = personComparer; // 可以把IComparer<Person>隐式转换为IComparer<Student> 但不能这样: IComparer<Person> personComparer = null; IComparer<Student> studentComparer = null; personComparer = studentComparer; // 无法将IComparer<Student>隐式转换为IComparer<Person> 4、小结
C#中协变和逆变的设计在C#4.0的基础类库中,一些接口的泛型参数分别用了in或out修饰,比如: public interface IEnumerator<out T> : IDisposable, IEnumerator { T Current { get; } } 而另一些却没有: public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable { T this[int index] { get; set; } void Insert(int index, T item); void RemoveAt(int index); } 那么问题来了: 1、为什么 IComparable<in T> 被声明成逆变的而 IEquatable<T> 却被声明成不可变的? 2、为什么 IList<T> 被声明为不可变的? 简单来说,既然协变的接口的泛型参数只能作为函数的返回值,而逆变的接口的泛型参数只能作为函数的参数,那么像 IList<T> 这种 T 既要做为返回值又要作为参数的情况,自然只能声明为不可变的了。 3、为什么一个泛型参数不可以即是协变的又是逆变的? 简单来说是为了在编译期进行类型安全检查。
本文参考:http://www.cnblogs.com/1-2-3/archive/2010/09/27/covariance-contravariance-csharp4.html |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论