Covarianza e IList

Me gustaría una colección covariante cuyos elementos se pueden recuperar por índice. IEnumerable es la única colección .net que conozco que es covariante, pero no tiene este soporte de índice.

Específicamente, me gustaría hacer esto:

List dogs = new List(); IEnumerable animals = dogs; IList animalList = dogs; // This line does not compile 

Ahora, soy consciente de por qué esto es un problema. List implementa ICollection que tiene un método Add. Al lanzar contenido a IList of Animals, permitiría que el código subsiguiente agregue cualquier tipo de animal que no esté permitido en la colección “real” List .

Entonces, ¿alguien sabe de una colección que admite búsquedas de índice que también es covariante? Me gustaría no crear el mío.

Actualización: desde .NET 4.5 en adelante, IReadOnlyList y IReadOnlyCollection son ambos covariantes; El último es básicamente IEnumerable más Count ; el primero agrega T this[int index] {get;} . También se debe tener en cuenta que IEnumerable es covariante desde .NET 4.0 en adelante.

Tanto List como ReadOnlyCollection (a través de List.AsReadOnly() ) implementan ambos.


Solo puede ser covariante si solo tiene un indexador get , es decir,

 public T this[int index] { get; } 

Pero todas las colecciones principales tienen {get;set;} , lo que lo hace incómodo. No conozco ninguno que sea suficiente allí, pero podría envolverlo , es decir, escribir un método de extensión:

 var covariant = list.AsCovariant(); 

que es un envoltorio alrededor de un IList que solo expone el IEnumerable y el get indexer …? debería ser solo unos minutos de trabajo …

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

Aquí hay una clase que escribí para abordar este escenario:

 public class CovariantIListAdapter : IList where TDerived : TBase { private IList source; public CovariantIListAdapter(IList source) { this.source = source; } public IEnumerator 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; } } } 

Ahora puedes escribir un código como este:

 List dogs = new List(); dogs.Add(new Dog { Name = "Spot", MaximumBarkDecibals = 110 }); IEnumerable animals = dogs; IList animalList = new CovariantIListAdapter(dogs); animalList.Add(new Dog { Name = "Fluffy", MaximumBarkDecibals = 120 }); 

Los cambios son visibles en ambas listas, porque realmente todavía hay solo 1 lista. La clase de adaptador solo pasa las llamadas, lanzando elementos según sea necesario para lograr la interfaz deseada IList .

Obviamente, si agrega cualquier cosa que no sea Dogs a animalList , arrojará una excepción, pero esto satisfizo mis necesidades.

A partir de .NET Framework 4.5, existe una interfaz IReadOnlyList que es covariante. Es esencialmente lo mismo que la interfaz IIndexedEnumerable en la respuesta de Mark Gravell.

IReadOnlyList se implementa de esta manera:

  ///  /// Represents a read-only collection of elements that can be accessed by index. ///  /// 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. public interface IReadOnlyList : IReadOnlyCollection, IEnumerable, IEnumerable { ///  /// Gets the element at the specified index in the read-only list. ///  /// ///  /// The element at the specified index in the read-only list. ///  /// The zero-based index of the element to get.  T this[int index] { get; } } 

Técnicamente, está la colección de arreglos. Está algo roto en su varianza, pero hace lo que preguntas.

 IList animals; List dogs = new List(); animals = dogs.ToArray(); 

Por supuesto, explotarás espectacularmente en tiempo de ejecución si tratas de poner un Tiger en la matriz en cualquier lugar.