2011-04-29 91 views
41

我想要一個Covariant集合,其中的項目可以通過索引檢索。 IEnumerable是我知道的唯一一個.net集合,它是Covariant,但它沒有這個索引支持。協變和IList

具體來說,我想這樣做:

List<Dog> dogs = new List<Dog>(); 

IEnumerable<Animal> animals = dogs; 
IList<Animal> animalList = dogs; // This line does not compile 

現在,我知道爲什麼這是一個問題。列表實現了具有Add方法的ICollection。通過向上鑄造動物的IList,它將允許隨後的代碼添加在「真實」List<Dog>集合中不允許的任何類型的動物。

那麼是否有人知道支持索引查找的集合也是協變的?我想不創造我自己的。

+6

+1 ......缺乏只讀在.NET(除了'IEnumerable')集合接口使得這幾乎是不可能的,但我想這是一個常見的用途 - 或許有人提出了一個可行的解決方案。 – 2011-04-29 12:44:14

+0

您可以使用IEnumerable <>和ElementAt()一起使用,儘管語法不會很漂亮。 – 2011-04-29 12:50:36

回答

44

更新:從.NET 4.5起,有IReadOnlyList<out T>IReadOnlyCollection<out T>它們都是協變的;後者基本上是IEnumerable<out T>Count;前者增加T this[int index] {get;}。還應該注意的是,從.NET 4.0起,IEnumerable<out T>是協變的。

List<T>ReadOnlyCollection<T>(通過List<T>.AsReadOnly())實現這兩個。


,如果它只有一個get索引它只能被協變,即

public T this[int index] { get; } 

但所有主要收藏有{get;set;},這使得這種尷尬的。我不知道有任何這就夠了那裏,但你可以它,即編寫擴展方法:

var covariant = list.AsCovariant(); 

這是一個IList<T>周圍的包裝,只有公開IEnumerable<T>get索引。 ..?應該只有幾分鐘的工作...

public static class Covariance 
{ 
    public static IIndexedEnumerable<T> AsCovariant<T>(this IList<T> tail) 
    { 
     return new CovariantList<T>(tail); 
    } 
    private class CovariantList<T> : IIndexedEnumerable<T> 
    { 
     private readonly IList<T> tail; 
     public CovariantList(IList<T> tail) 
     { 
      this.tail = tail; 
     } 
     public T this[int index] { get { return tail[index]; } } 
     public IEnumerator<T> GetEnumerator() { return tail.GetEnumerator();} 
     IEnumerator IEnumerable.GetEnumerator() { return tail.GetEnumerator(); } 
     public int Count { get { return tail.Count; } } 
    } 
} 
public interface IIndexedEnumerable<out T> : IEnumerable<T> 
{ 
    T this[int index] { get; } 
    int Count { get; } 
} 
+0

@ user731000 - 請參閱更新 – 2011-04-29 12:48:40

+0

謝謝。這似乎是最好的前進方向。 – 2011-04-29 16:22:29

+0

我有時希望微軟已經設法讓IList繼承自子接口IReadableByIndex(您稱爲IIndexedEnumerable),IWritableByIndex和iAppendable,以便允許有用的協變和逆變。不幸的是,還沒有辦法通過讀寫屬性來實現只讀或只寫屬性。如果可以這樣做,協變/逆變子接口的所有成員自然會被IList的任何有效實現所實現,所以可以在不破壞現有代碼的情況下添加子接口。 – supercat 2011-06-14 20:52:33

2

從技術上講,這是數組集合。它的差異有點不一樣,但它確實符合你的要求。

IList<Animal> animals; 
List<Dog> dogs = new List<Dog>(); 
animals = dogs.ToArray(); 

你會的,當然,炸燬而壯觀地在運行時,如果你試圖把陣列中的任何地方Tiger

6

這裏有一類我寫來解決這樣的情景:

public class CovariantIListAdapter<TBase, TDerived> : IList<TBase> 
    where TDerived : TBase 
{ 
    private IList<TDerived> source; 

    public CovariantIListAdapter(IList<TDerived> source) 
    { 
     this.source = source; 
    } 

    public IEnumerator<TBase> GetEnumerator() 
    { 
     foreach (var item in source) 
      yield return item; 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return GetEnumerator(); 
    } 

    public void Add(TBase item) 
    { 
     source.Add((TDerived) item); 
    } 

    public void Clear() 
    { 
     source.Clear(); 
    } 

    public bool Contains(TBase item) 
    { 
     return source.Contains((TDerived) item); 
    } 

    public void CopyTo(TBase[] array, int arrayIndex) 
    { 
     foreach (var item in source) 
      array[arrayIndex++] = item; 
    } 

    public bool Remove(TBase item) 
    { 
     return source.Remove((TDerived) item); 
    } 

    public int Count 
    { 
     get { return source.Count; } 
    } 

    public bool IsReadOnly 
    { 
     get { return source.IsReadOnly; } 
    } 

    public int IndexOf(TBase item) 
    { 
     return source.IndexOf((TDerived) item); 
    } 

    public void Insert(int index, TBase item) 
    { 
     source.Insert(index, (TDerived) item); 
    } 

    public void RemoveAt(int index) 
    { 
     source.RemoveAt(index); 
    } 

    public TBase this[int index] 
    { 
     get { return source[index]; } 
     set { source[index] = (TDerived) value; } 
    } 
} 

現在你可以這樣寫代碼:

List<Dog> dogs = new List<Dog>(); 
dogs.Add(new Dog { Name = "Spot", MaximumBarkDecibals = 110 }); 

IEnumerable<Animal> animals = dogs; 
IList<Animal> animalList = new CovariantIListAdapter<Animal, Dog>(dogs); 

animalList.Add(new Dog { Name = "Fluffy", MaximumBarkDecibals = 120 }); 

的變化是兩個列表中可見,因爲沒什麼仍然只有1名單。適配器類僅傳遞通話,根據需要投射項目以實現所需的IList<TBase>界面。

很明顯,如果你添加了除狗之外的任何東西到animalList,它會拋出一個異常,但這符合我的需求。

+0

你不需要爲此創建一個新的適配器。只需創建一個新列表。像這樣:新名單(狗),一切都會正常工作。你可以儘管這不會解決矛盾問題。 – 2015-07-17 18:47:53

+0

它是多態的,即使你添加了一個不同於Animal的對象。此鏈接https://msdn.microsoft.com/en-us/library/ee207183.aspx描述協變和逆變 – natnael88 2016-01-29 15:50:08

3

從.NET Framework 4.5開始,存在一個接口IReadOnlyList,它是協變的。它與Mark Gravell的答案中的IIndexedEnumerable接口基本相同。

IReadOnlyList實現這樣的:

/// <summary> 
    /// Represents a read-only collection of elements that can be accessed by index. 
    /// </summary> 
    /// <typeparam name="T">The type of elements in the read-only list. This type parameter is covariant. That is, you can use either the type you specified or any type that is more derived. For more information about covariance and contravariance, see Covariance and Contravariance in Generics.</typeparam> 
    public interface IReadOnlyList<out T> : IReadOnlyCollection<T>, IEnumerable<T>, IEnumerable 
     { 
     /// <summary> 
     /// Gets the element at the specified index in the read-only list. 
     /// </summary> 
     /// 
     /// <returns> 
     /// The element at the specified index in the read-only list. 
     /// </returns> 
     /// <param name="index">The zero-based index of the element to get. </param> 
     T this[int index] { get; } 
     }