• 协变 和 逆变


    摘要

        ● 协变和逆变的定义是什么?给我们带来了什么便利?如何应用?
      ● 对于可变的泛型接口,为什么要区分成协变的和逆变的两种?只要一种不是更方便吗?
      ● 为什么还有不可变的泛型接口,为什么有的泛型接口要故意声明成不可变的?
      ● 复合的可变泛型接口遵循哪些规则?
      ● 协变和逆变的数学定义是什么?如何利用数学模型解释C#4里的协变和逆变的规则?

    前言

        协变和逆变是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、不可变

        如果一个接口的泛型参数没有inout修饰符,它就是不可变的。比如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 IComparable<in T> { int CompareTo(T other); }

        而另一些却没有:

    public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable
    {
        T this[int index] { get; set; }
    int IndexOf(T item); void Insert(int index, T item); void RemoveAt(int index); }
    public interface IEquatable<T> { bool Equals(T other); }

        那么问题来了:

        1、为什么 IComparable<in T> 被声明成逆变的而 IEquatable<T> 却被声明成不可变的?

        2、为什么 IList<T> 被声明为不可变的?

        简单来说,既然协变的接口的泛型参数只能作为函数的返回值,而逆变的接口的泛型参数只能作为函数的参数,那么像 IList<T> 这种 T 既要做为返回值又要作为参数的情况,自然只能声明为不可变的了。

        3、为什么一个泛型参数不可以即是协变的又是逆变的?

        简单来说是为了在编译期进行类型安全检查。

    本文转自:https://www.cnblogs.com/wangwust/p/6823431.html

  • 相关阅读:
    软件工程提问回顾与个人总结
    OO第二次单元总结
    OO第一次单元总结
    软件工程结对项目
    软件工程第一次阅读作业
    软件工程第0次个人作业
    结队作业
    软件工程第一次阅读作业
    软工第0次作业
    oo第四篇博客作业
  • 原文地址:https://www.cnblogs.com/YourDirection/p/12767599.html
Copyright © 2020-2023  润新知