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();
}
參考資料: