Covariance and Contravariance in Generics

 

一、Covariant example

using System;
using System.Collections.Generic;

namespace ConsoleApp1
{
    class Program
    {
        static void Main()
        {
            Console.ReadKey();
        }
    }

    //父類別
    class Base
    {
        public static void PrintBases(IEnumerable<Base> bases)
        {
            foreach (Base b in bases)
            {
                Console.WriteLine(b);
            }
        }
    }

    //子類別
    class Derived : Base
    {
        public static void Main()
        {
            List<Derived> dlist = new List<Derived>();

            Derived.PrintBases(dlist);
            IEnumerable<Base> bIEnum = dlist;
        }
    }
}

 

IEnumrable<T> 的原型宣告

public interface IEnumerable<out T> : IEnumerable
{
    //
        // Summary:
        //     Returns an enumerator that iterates through the collection.
        //
        // Returns:
        //     An enumerator that can be used to iterate through the collection.
        IEnumerator<T> GetEnumerator();
}

型別 T 在這個泛型介面中只能用於 method 的傳回值,而不能用來當作 method 的參數。

 

二、Contravariance example

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            ConvertMethod<string> aConvertMeth = UppercaseString;
            string name = "Dakota";
            Console.WriteLine(aConvertMeth(name));

            Console.ReadKey();
        }

        delegate string ConvertMethod<in T>(T a);
        private static string UppercaseString(string inputString)
        {
            return inputString.ToUpper();
        }
    }
}

Contravariance 只會出現在泛型介面和泛型委派的場合。

 

再來看一個「裝忙」的範例

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            TestClass<Derived> Derived = new TestClass<Derived>();
            ITest<Base> IBase = new ShowValue();
            Derived.Show(IBase);

            Console.ReadKey();
        }
    }

    public class TestClass<T>
    {
        public void Show(ITest<T> s) { }
    }

    //父類別          
    public class Base
    {
        public string value { get; set; }
    }

    //子類別
    public class Derived : Base { }

    public interface ITest<in T>
    {
        string Full(T x, T y);
    }

    public class ShowValue : ITest<Base>
    {
        public string Full(Base x, Base y)
        {
            return x.value + y.value;
        }
    }
}

注意此範例中的 Derived.Show(IBase) 方法呼叫。在撰寫這行敘述時,

Visual Studio 的 Intellisense 功能會提示這裡的 Derived.Show 方法需要的參數型別是 IComparer<Derived>,

但我們傳入的參數卻是 IComparer<Base>。也就是說,

這裡會發生「把較父型別指派給較子型別」的動作(跟 covariance 正好相反)。

之所以能夠這樣寫,是因為 ICompare<T> 的泛型參數 T 在

C# 4.0 加入了 contravariance 的宣告(關鍵字 in)

 

三、小結

Covariant (協變) - 用子類型的實體去操作父類型的方法被稱為協變。

Contravariance (逆變) - 用父類型的實體(介面)去操作子類型的方法被稱為逆變。

Invariance (不變) - 只能用原來的型態去操作原來型態的方法。

 

Covariance 限定用於輸出的場合,contravariance 則是指泛型參數型別只能用於輸入的場合(方法的傳入參數)。

// (contravarience) 型別 T 在這裡只能用於輸入參數,不可用於方法的傳回值(特別指回傳型別 T)。
public interface IFoo<in T>
{
    void SetData(T obj);
}

// (Covarience) 型別 T 在這裡只能用於方法的傳回值,不可用於輸入參數。
public interface IFoo2<out T>
{
    T GetData();
}

 

參考資料:

Covariance and Contravariance in Generics