2011-04-29 67 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; } 
     }