C#基礎筆記——協變(Covariance)和逆變(Contravariance)

weixin_30924079發表於2020-04-04

一、概述

協變是指返回型別返回比宣告的型別派生程度更大的型別,關鍵字: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支援更多的應用場景。

 

 

 

轉載於:https://www.cnblogs.com/Abel-Zhang/p/Covariance.html

相關文章