這篇文章討論設計泛型介面的時候,共變與逆變的設計時機。
變異性向來是個不太好理解的問題,扯到泛型的時候就更容易讓人摸不著頭緒。
先來聊聊變異性,變異性 (Variance) 分為共變 (Covariant) 與 逆變 (Contravariant,微軟文件稱之為反變)。簡單來說,共變的意思就是『在使用衍生型別的地方可以使用基底型別替代』;而逆變則是反過來『在使用基底型別的地方可以使用衍生型別替代』。
C# 中只有泛型介面和泛型委派的型別參數容許變異性,泛型類別是不允許的。不談太多的深刻道理,簡單聊一下設計泛型介面的時候,哪種情況可以將泛型參數設計成共變或逆變。
共變 Covariant
C# 中泛型參數要設定為共變的宣告是 out 泛型修飾詞,典型的例子就是 IEnumerable<T>,參考 Microsoft Docs IEnumerable<T> Interface 可以看到它的宣告如下,這表示該 T 型別具有共變性:
public interface IEnumerable<out T> : System.Collections.IEnumerable
當 T 型別在該泛型介面的內部成員中僅用於回傳值時,該 T 型別可以宣告為共變性,例如:
public interface ICovariance<out T>
{
// 作為只有 getter 的屬性型別
T Data { get; }
// 作為方法的回傳型別
T GetResult();
}
逆變
對於逆變是使用 in 泛型修飾詞,典型的例子就是 IComparable<T>,參考 Microsoft Docs IComparable<T> Interface 可以看到它的宣告如下,這表示該 T 型別具有逆變性:
public interface IComparable<in T>
當 T 型別在該泛型介面的內部成員中僅用於方法的傳入參數時,該 T 型別可以宣告為逆變性,例如:
public interface IContravariance<in T>
{
// 作為只有 setter 的屬性型別
T Data { set; }
// 作為方法的傳入型別
bool EqualsTo(T other);
}
很容易判斷對吧;另外呢,同一個 T 型別不可以同時是 in 又是 out,若一個 T 型別在方法內部同時作為回傳值與傳入參數的型別使用,則該 T 型別只能保持不變性,例如:
public interface IInvariance1<T>
{
T Data { get; set; }
}
public interface IInvariance2<T>
{
T GetResult();
bool EqualsTo(T other);
}
如果泛型介面擁有兩個以上的泛型參數,那有可能 T1 是 in , T2 是 out (或其他排列組合):
public interface IDual<out T1,in T2>
{
T1 Data { get; }
bool EqualsTo(T2 other);
}