標籤:

C# 中為什麼List<List<T>> 不能轉換為 IList<IList<T>> ?


因為IList&的泛型參數T是invariant的,而不是題主所期待的covariant(或者說IList&):

public interface IList& : ICollection&, IEnumerable&, IEnumerable

從 List&&> 轉成 IList&&> 是完全沒問題的,因為這個轉換從泛型參數的角度看是invariant的。然而進一步轉成 IList&&> 就不行了,因為把泛型參數中(內層)的 List& 轉成 IList& 需要covariant。

傳送門:Covariance and Contravariance in Generics

具體到題主的例子,其實這樣就好了:

using System.Collections.Generic;
using System.Linq;

class Record {
public List& Data {
get { return null; }
}
}

class Program
{
static void Main(string[] args) {
var records = new List&();
var casted1 = (IList&&>)records.Select(r =&> r.Data as IList&).ToList(); // ok
Console.WriteLine("cast1 succeeded.");
var casted2 = (IList&&>)records.Select(r =&> r.Data).ToList&&>(); // ok
Console.WriteLine("cast2 succeeded.");
var casted3 = (IList&&>)records.Select(r =&> r.Data).ToList(); // fail
Console.WriteLine("cast3 succeeded.");
}
}

這是因為我的例子中,ToList()其實是個泛型方法,其泛型參數通常是被自動推導出來的;Record.Data的類型是List&,於是用在那個ToList()調用時推導出來的就是ToList&&>。我們要麼在Select()的時候就as一下來讓Select推導出來的類型改變,要麼直接在ToList()的地方指定清楚泛型參數,就好啦。


C#裡面只有數組可以做類似的錯誤轉換。不能賺的原因很簡單,IList&有很多種,又不一定是List&。如果你能轉成IList&&>,那麼我就給你Add一個我自己實現的IList&,然後轉回List&&>,拿出來那應該是什麼?


List& 的泛型參數不支持協變和逆變,從 List& 協變為 IList& 如果不涉及泛型參數的變化,這是允許的。所以我們就可以只討論 IList& 泛型參數的問題。

舉個例子,有一個方法接收 IList& 參數,你不能傳遞一個 List& (既 IList&) 進去,為什麼?萬一這個函數往裡面 Add 了一個 CheckBox 了呢?當然你也不能傳遞一個 List& 進去,為什麼?這個顯而易見,不是所有的 object 都有 Control 特有的 Enabled 屬性的,類型根本無法保證兼容。IList 之所以這樣做還是為了類型安全。

當然了,如果你能保證不往 List 里加別的東西,支持協變的泛型介面也有,就比如前面答主提到的 IReadOnlyList&,因為你無法像上面提到的那樣向一個 IReadOnlyList& 中添加一個 Base 對象,集合類內部不會出現逆變不合法的情況,能夠保證類型安全。


我來這麼舉例吧:

已知Collection&也是一個IList&,且不繼承自List&

那麼因為IList&&>支持Add(IList&),所以對其進行Add(Collection&)是可行的。然而由於Collection&不是List&,那麼對List&&>進行Add(Collection&)是非法的。

由此可見List&&>不滿足IList&&>的介麵條件,故不能轉換。證畢。

另外,這種covariant的轉換在out-only的介面上是可行的。既,List&&>可以轉換為IReadOnlyList&&>。


推薦閱讀:

為什麼很多人認為.NET就是拖控制項?
C#為什麼總不被看好?
如何提高unity開發水平?
編程該怎麼學下去(C#)?

TAG:C# |