Подтвердить что ты не робот

Ковариация и IList

Мне нужна коллекция 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>.

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

4b9b3361

Ответ 1

Обновление: начиная с .NET 4.5 существует IReadOnlyList<out T> и IReadOnlyCollection<out T>, которые являются ковариантными; Последнее в основном IEnumerable<out T> plus 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 indexer...? должно быть всего несколько минут работы...

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; }
}

Ответ 2

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

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, оно выдает исключение, но это соответствует моим потребностям.

Ответ 3

В .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

Технически, существует массив. Это своего рода нарушение в своей дисперсии, но оно делает то, что вы просите.

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

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