• C#基础笔记——协变(Covariance)和逆变(Contravariance)


    一、概述

    协变是指返回类型返回比声明的类型派生程度更大的类型,关键字:out。

    逆变是指方法的参数可以是委托或者泛型接口的参数类型的基类,关键字:in。FCL4.0中支持逆变的常用委托有:Func<in T,out TResult>,Predicate<in T>。

    二、泛型中协变事例

       class Program
        {
            static void Main(string[] args)
            {
                ISalary<Programer> s=new BaseSalaryCounter<Programer>();
                printSalary(s);
                Console.ReadKey();
            }
    
            private static void printSalary(ISalary<Employee> s)
            {
                 s.Pay();
            }
        }
        //interface ISalary<T>//这样写会导致编译报错
        interface ISalary<out T>
        {
            void Pay();
        }
        class BaseSalaryCounter<T> : ISalary<T>
        {
            public void Pay()
            {
                Console.WriteLine("Pay Base salary");
            }
        }
    
        class Employee
        {
            public string Name { get ;set; }
        }
    
        class Programer:Employee
        {
            //
        }
    
        class Manager:Employee
        {
            //
        }

    编辑器对于接口和委托类型参数的检查是非常严格的,若不使用"out”关键字会导致编译失败。

    参数类型ISalary<Employee>无法接收ISalary<Programer>类型变量。

    当我们使用"out”关键字后,ISalary<out T>就赋予了协变性,可以实现返回类型返回比声明的类型派生程度更大的类型。

    三. 委托中的协变

    其实委托中的泛型变量天然是部分支持协变的。

    让我们看一下下面的事例:

    class Program
        {
            public delegate T GetEmployeeHandler<T>(string name);
    static void Main(string[] args) { GetEmployeeHandler<Employee> getAEmployee = GetAManager; Employee e = getAEmployee("Abel"); Console.ReadKey(); } private static Manager GetAManager(string name) { Console.WriteLine("我是经理:" + name); return new Manager() { Name = name }; } private static Employee GetAEmployee(string name) { Console.WriteLine("我是员工:" + name); return new Employee() { Name = name }; } } class Employee { public string Name { get ;set; } } class Programer:Employee { // } class Manager:Employee { // }

    以上代码编译是不会有问题的,执行结果是:我是经理:Abel

    我们也许认为委托中的泛型变量不再需要out关键字,这是错误的,因为下面情况编译是通不过的:

    class Program
        {
            public delegate T GetEmployeeHandler<T>(string name);
    
            static void Main(string[] args)
            {
                GetEmployeeHandler<Manager> getAManager = GetAManager;
                GetEmployeeHandler<Employee> getAEmployee = getAManager;
                Employee e = getAEmployee("Abel");
            }
    
            private static Manager GetAManager(string name)
            {
                Console.WriteLine("我是经理:" + name);
                return new Manager() { Name = name };
            }
    
            private static Employee GetAEmployee(string name)
            {
                Console.WriteLine("我是员工:" + name);
                return new Employee() { Name = name };
            }
        }
    
        class Employee
        {
            public string Name { get ;set; }
        }
    
        class Programer:Employee
        {
            //
         }
    
        class Manager:Employee
        {
            //
         }

    要让上面代码通过必须为委托中的泛型参数指定out关键字:

    public delegate T GetEmployeeHandler<out T>(string name);

    四. 为泛型类型参数指定逆变事例:

        class Program
        {
            static void Main(string[] args)
            {
                Programer p = new Programer() { Name = "Tony" };
                Manager m = new Manager() { Name = "Abel" };
                Test(p, m);
                Console.ReadKey();
            }
    
            public static void Test<T>(IMyComparable<T> t1, T t2)
            {
                Console.WriteLine(t1.Compare(t2) == 1 ? "Abel名字排在Tony前" : "Tony名字排在Abel前");
            }
    
        }
       
        public interface IMyComparable<T>
        {
            int Compare(T other);
        }
    
        class Employee : IMyComparable<Employee>
        {
            public string Name { get ;set; }
    
            public int Compare(Employee other)
            {
                return Name.CompareTo(other.Name);
            }
        }
    
        class Programer : Employee, IMyComparable<Programer>
        {
            public int Compare(Programer other)
            {
                return Name.CompareTo(other.Name);
            }
        }
    
        class Manager : Employee, IMyComparable<Manager>
        {
            public int Compare(Manager other)
            {
                return Name.CompareTo(other.Name);
            }
        }

    上面的事例中,如果不为接口IMyComparable的泛型参数T指定in关键字,将会导致Test(p, m)编译报错。

    因为引入了逆变性这让方法Test支持更多的应用场景。

  • 相关阅读:
    【设计模式
    【设计模式
    【设计模式
    【设计模式
    【设计模式
    【设计模式
    实干猪
    Mysql 千万级快速查询|分页方案
    如何成为一名优秀的CTO(首席技术官)
    成为优秀程序员的10个有效方法
  • 原文地址:https://www.cnblogs.com/Abel-Zhang/p/Covariance.html
Copyright © 2020-2023  润新知