Ковариантность и IList - PullRequest
       20

Ковариантность и IList

45 голосов
/ 29 апреля 2011

Я бы хотел коллекцию Covariant, элементы которой можно получить по индексу.IEnumerable - это единственная коллекция .net, о которой я знаю, это Covariant, но он не поддерживает этот индекс.

В частности, я хотел бы сделать это:

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

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

Теперь я знаю, почему это проблема.Список реализует ICollection, который имеет метод Add.При повышении до IList Животных это позволило бы последующему коду добавить любой тип животного, которое не разрешено в «реальной» коллекции List<Dog>.

Так что любой знает о коллекции, которая поддерживаетпоиск по индексу, который также является ковариантным?Я не хотел бы создавать свои собственные.

Ответы [ 4 ]

54 голосов
/ 29 апреля 2011

Обновление: начиная с .NET 4.5 и далее IReadOnlyList<out T> и IReadOnlyCollection<out T>, оба являются ковариантными;Последний в основном IEnumerable<out T> плюс Count;бывший добавляет T this[int index] {get;}.Также следует отметить, что IEnumerable<out T> является ковариантным начиная с .NET 4.0.

Оба 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; }
}
7 голосов
/ 29 июля 2013

Вот класс, который я написал для решения этого сценария:

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> интерфейса.

Очевидно, что если вы добавите что-либо, кроме Dogs, к animalList, это вызовет исключение, но это удовлетворило мои потребности.

4 голосов
/ 14 ноября 2013

Начиная с .NET Framework 4.5, существует интерфейс IReadOnlyList, который является ковариантным. По сути, он такой же, как интерфейс 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; }
      }
4 голосов
/ 29 апреля 2011

Технически, есть коллекция массивов.Он как бы разбит по своей дисперсии, но выполняет то, что вы просите.

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

Вы, конечно, довольно эффектно взорвётесь во время выполнения, если попытаетесь поместить Tiger в массив где-либо.

...