前言
在引用類型系統時,協變、逆變和不變性具有如下定義。 這些示例假定一個名為 Base 的基類和一個名為 Derived的派生類。
- Covariance
使你能夠使用比原始指定的類型派生程度更大的類型。
你可以將 IEnumerable
- Contravariance
使你能夠使用比原始指定的類型更泛型(派生程度更小)的類型。
你可以將 Action 的實例分配給 Action
- Invariance
表示只能使用最初指定的類型。 固定泛型類型參數既不是協變,也不是逆變。
你無法將 List 的實例分配給 List
以上來自於官方文檔對協變、逆變、不變性的解釋
為啥C#需要協變和逆變?
我們首先來看一段代碼:
class FooBase{ }
class Foo : FooBase
{
}
var foo = new Foo();
FooBase fooBase = foo;
//以下代碼在.NET 4.0之前是不被支持的
IEnumerable<Foo> foo = new List<Foo>();
IEnumerable<FooBase> fooBase = foo;
因此,在這里實際上可以回答,C#的協變和逆變就是主要有兩種目的:
- 兼容性:.NET2.0就推出了泛型,而從.NET 2.0到.NET 3.5期間不支持對泛型接口中的占位符
T
支持隱式轉換,因此在.NET4.0推出協變和逆變 - 為了支持更廣泛的隱式類型的轉換,在這里就是在泛型體系中支持
在C#中,目前只有泛型接口和泛型委托可以支持協變和逆變,
協變(Covariance)
內置的泛型協變接口,IEnumerator<T>
、IQuerable<T>
、IGrouping<Tkey, TElement>
:
public interface IEnumerable<out T> : IEnumerable
{
new IEnumerator<T> GetEnumerator();
}
public interface IQueryable<out T> : IEnumerable<T>, IEnumerable, IQueryable
{
}
public interface IGrouping<out TKey, out TElement> : IEnumerable<TElement>, IEnumerable
{
TKey Key { get; }
}
因此這段代碼在.NET4.0及以上版本將不會編譯報錯:
IEnumerable<Foo> foo = new List<Foo>();
IEnumerable<FooBase> fooBase = foo;
實際上,對於協變,有下面的約束,否則則會在編譯時報錯:
- 泛型參數占位符以
out
關鍵子標識,並且占位符T
只能用於只讀屬性、方法或者委托的返回值,out
簡而易懂,就是輸出的意思 - 當要進行類型轉換,占位符
T
要轉換的目標類型也必須是其基類,上述例子則是Foo隱式轉為FooBase
逆變(Contravariance)
內置的泛型逆變委托Action
、Func
、Predicate
,內置的泛型逆變接口IComparable<T>
、IEquatable<T>
:
public delegate void Action<in T>(T obj);
public delegate TResult Func<in T, out TResult>(T arg);
public delegate bool Predicate<in T>(T obj);
public interface IComparable<in T>
{
int CompareTo(T? other);
}
public interface IEquatable<T>
{
bool Equals(T? other);
}
而逆變的用法則是這樣:
Action<FooBase> fooBaseAction = new Action<FooBase>((a)=>Console.WriteLine(a));
Action<Foo> fooAction = fooBaseAction;
而對於逆變,則跟協變相反,有下面的約束,否則也是編譯時報錯:
- 要想標識為逆變,應該是要在占位符
T
前標識in
,只能用於只寫屬性、方法或者委托的輸入參數 - 當要進行類型轉換,占位符
T
要轉換的目標類型也必須是其子類,上述例子則是FooBase轉為Foo
總結
- 協變和逆變只對泛型委托和泛型接口有效,對普通的泛型類和泛型方法無效
- 協變和逆變的類型必須是引用類型,因為值類型不具備繼承性,因此類型轉換存在不兼容性
- 泛型接口和泛型委托可同時存在協變和逆變的類型參數,即占位符
T
參考
- 泛型中的協變和逆變 | Microsoft Docs
- 《你必須知道的.NET(第2版)》