本篇文章主要講述 in 和 out 兩個泛型參數修飾符。對於這兩個關鍵字,樓主也是各種不懂,要學會使用這兩個關鍵字,必須先理解協變和逆變的概念。 協變和逆變 簡單定義就是:協變就是泛型接口從子類向父類轉化,逆變就是父類向子類轉換。下面兩種轉換就是協變和逆變: [csharp] IEnumerable<string> strs = null; IEnumerable<object> objs = null; objs = strs; // “子類”向“父類”轉換,即泛型接口的協變 strs = objs; // “父類”向“子類”轉換,即泛型接口的逆變,這個會報錯,IEnumerable 接口用 out 修飾泛型參數,限定了只能協變,這裡只是為了說明兩個轉換的區別 上面的代碼,IEnumerable<string> 和 IEnumerable<object> 表面上看有點類似父類和子類的關系,實際上他們根本沒有任何繼承關系,可以認為是兩個獨立的類。而 .NET 4.0開始,有條件的允許上面協變和逆變的轉換,這裡就用到 in 或 out 關鍵字修飾泛型參數 T 的使用范圍。 可以看下 IEnumerable 的聲明 [csharp] [TypeDependency("System.SZArrayHelper")] public interface IEnumerable<out T> : IEnumerable { IEnumerator<T> GetEnumerator(); } 如果 IEnumerable 接口用 in 修飾泛型參數的話,那麼逆變(即 strs = objs;) 是可以通過編譯的。所以我們總結起來就是: 用 out 來修飾泛型參數時允許協變,用 in來修飾泛型參數允許逆變。 說到這裡,這兩個關鍵字倒沒什麼講的,那為什麼.NET 4.0開始引入協變逆變。 [csharp] List<object> list = new List<object>(); list.AddRange(strs); // 如果沒有協變(子類對於父類的隱式轉換),這行代碼會報錯 list.AddRange(objs); 上面代碼,如果IEnumerable 接口沒有用 out 修飾泛型參數,那就會編譯報錯。而且你得寫個循環,將 strs 一個個轉換為 IEnumerable<object>,為了避免這樣的麻煩,支持協變的話,就可以直接轉換,不會編譯報錯。所有協變逆變的問題實際上都源於一個根本的原則: 子類可以向父類隱式轉換,父類不能向子類隱式轉換。 最後再看一個完整的例子,來看看 in 和 out 的使用方法: [csharp] class Program { static void Main(string[] args) { IContravariant<Object> iobj1 = new Sample<Object>(); IContravariant<String> istr1 = new Sample<String>(); ICovariant<Object> iobj2 = new Sample<Object>(); ICovariant<String> istr2 = new Sample<String>(); istr1 = iobj1; // in 修飾符實現協變(子類對於父類的隱式轉換) iobj2 = istr2; // out 修飾符實現逆變(父類對於子類的隱式轉換) } } // 逆變接口. interface IContravariant<in A> { } // 協變接口. interface ICovariant<out R> { } // 實現協變逆變接口的類. class Sample<A> : IContravariant<A> ,ICovariant<A> { }