C#中逆變與協變一般應用到以下三種場景
一、數組的協變
數組的協變允許派生程度更大的類型的數組隱式轉換爲派生程度更小的類型的數組。
比如:Object[] os = new String[5];
二、泛型接口的逆變與協變
public class Program
{
static void Main(string[] args)
{
I<C2, C2> i = new U<C1, C3>();
}
}
class C1{}
class C2:C1{}
class C3:C2{}
interface I<in P, out R>
{
R Func(P p);
}
class U<P, R> : I< P, R>
{
public R Func(P p)
{
throw new NotImplementedException();
}
}
上面的代碼中,接口I同時應用了逆變和協變。
先說逆變:逆變允許 方法具有 與接口的泛型類型參數(實例中的 in P)所定義的參數類型(P作爲Func的參數類型)相比,派生程度更大的返回類型。
在Main函數中,我們給定泛型接口I的類型參數中的R爲C2,但實際的實現卻是C1,並正常運行。爲什麼會發生這種情況呢?因爲,
我們給定的參數類型是C2,也就是說變量i在其它調用者的眼裏,含有一個參數類型爲C2的方法Func,但i的骨子裏卻是用C1實現的,這樣有何不可呢?
客戶代碼提供給i.Func的參數的類型是C2,而C2可以隱式轉化爲C1,一點也不影響使用。
再說協變,同樣的道理,Func的返回值肯定是要用在適和C2類型的變量的地方,那麼i.Func提供給客戶代碼一個C3類型的變量,又何樂不爲呢?
三、委託的逆變和協變
對於普通委託:例如,
delegate C2 F1(C2 c);
F1 f1 = new U<C1, C3>().Func;
不需要說明什麼,可以很自然地應用逆變與協變
但是對於泛型委託,例如,
delegate R F2<in P,out R>(P p);
F2<C2, C2> f2 = new U<C1, C3>().Func;
可以使用in和out關鍵字指定類型參數的逆變或協變,也可以不使用,兩者沒有什麼區別。但需要注意的是,要麼使用正確,要麼不使用,否則會出現編譯錯誤。
正確的作法是,在方法中作爲參數的類型逆變,使用in關鍵字,作爲返回值的類型協變,使用out關鍵字。
另外,還需要注意的是,當委託規定方法的參數有ref和out修飾時,該參數無法應用逆變,比如:
delegate C2 F0(ref C2 c);
F0 f0 = new U<C1, C3>().Func;//編譯錯誤