#c# #.net #.net-4.0 #covariance
#c# #.net #.net-4.0 #ковариация
Вопрос:
Я хотел бы получить ковариантную коллекцию, элементы которой можно извлекать по индексу. IEnumerable — единственная известная мне коллекция .net, которая является ковариантной, но у нее нет поддержки этого индекса.
В частности, я хотел бы сделать это:
List<Dog> dogs = new List<Dog>();
IEnumerable<Animal> animals = dogs;
IList<Animal> animalList = dogs; // This line does not compile
Теперь я понимаю, почему это проблема. List реализует, ICollection
который имеет метод Add. Приведение к IList
из Animals позволило бы последующему коду добавлять любой тип animal, который не разрешен в «реальной» List<Dog>
коллекции.
Итак, кто-нибудь знает о коллекции, которая поддерживает поиск по индексу, который также является ковариантным? Я бы не хотел создавать свой собственный.
Комментарии:
1. 1 … отсутствие интерфейсов сбора данных только для чтения в .NET (кроме
IEnumerable
) делает это практически невозможным, но я полагаю, что это распространенный вариант использования, возможно, кто-то все-таки придумал приемлемое решение.2. Вы можете использовать IEnumerable<> вместе с ElementAt() , хотя синтаксис не будет таким красивым.
Ответ №1:
Обновление: начиная с .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; }
}
Комментарии:
1. Спасибо. Это кажется наилучшим способом продвижения вперед.
2. Иногда я жалел, что Microsoft не придумала способ наследовать IList от вложенных интерфейсов IReadableByIndex (которые вы называете IIndexedEnumerable), IWritableByIndex и iAppendable, чтобы обеспечить полезную ковариацию и контравариантность. К сожалению, пока еще нет способа реализовать свойство, доступное только для чтения или записи, с помощью свойства чтения-записи. Если бы это можно было сделать, все элементы ковариантных / контравариантных подинтерфейсов, естественно, были бы реализованы любой допустимой реализацией IList, поэтому подинтерфейсы могли быть добавлены без нарушения существующего кода.
3. Учитывая, что такая вещь невозможна, ваш подход, возможно, является лучшим способом продвижения вперед. Я предполагаю, что до появления covariance универсальный IList, возможно, был не так уж плох, но необходимость наличия индексатора чтения-записи, а не индексатора чтения и индексатора записи, делает co / contra-variance бесполезным для большинства коллекций. Кстати, мне интересно, почему что-то со свойством только для чтения и только для записи, но без индексатора чтения-записи, не может быть красиво прочитано и записано? Почему компилятор не может выбрать наилучшее свойство, которое действительно будет работать?
4. Потрясающий ответ. Просто хотел отметить, что, несмотря на то, что IEnumerable<out T> стал ковариантным начиная с .NET 4.0, IList<T> по-прежнему остается инвариантным, начиная с сегодняшнего .NET Standard 2.1, поэтому Microsoft вынуждает нас придерживаться IReadOnlyList<out T> .
5.@Lesair это не упущение; это потому, что
IList<T>
не может быть ковариантным (или контравариантным), поскольку поверхность API (которая позволяет вам вводить значения и извлекать их) несовместима с требованиями дисперсии. Так что это не Microsoft вынуждает это; это природаIList<T>
Ответ №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>
интерфейса.
Очевидно, что если вы добавите в animalList
что-либо, кроме Dogs , это вызовет исключение, но это отвечало моим потребностям.
Комментарии:
1. вам не нужно создавать новый адаптер для этого. Просто создайте новый список. вот так: создайте список<Animal> (собаки), и все будет работать нормально. Вы можете, хотя это не решило бы проблему контраргументации.
2. Это полиморфизм, даже вы добавили объект, отличный от Animal. эта ссылка msdn.microsoft.com/en-us/library/ee207183.aspx расскажите о ковариации и контравариантности
3. по тому же шаблону вы можете написать ContravarianceIListAdapter. Рекомендация: напишите методы расширения, которые скрывают эти адаптеры, чтобы вы могли писать dogs. asList<Животное>()
Ответ №3:
Технически, существует коллекция массивов. Это своего рода нарушение в своей вариативности, но оно делает то, что вы просите.
IList<Animal> animals;
List<Dog> dogs = new List<Dog>();
animals = dogs.ToArray();
Вы, конечно, довольно эффектно взорветесь во время выполнения, если попытаетесь поместить Tiger
в массив где угодно.
Ответ №4:
Начиная с .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; }
}