2011-04-29 24 views
41

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.Covarianza e IList

En concreto, me gustaría hacer esto:

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

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

Ahora, estoy consciente de por qué esto es un problema. La lista implementa ICollection que tiene un método Add. Mediante la conversión a IList de Animales, permitiría el código posterior para agregar cualquier tipo de animal que no esté permitido en la colección "real" List<Dog>.

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

+6

+1 ... la falta de sólo lectura interfaces de la colección en .NET (aparte de 'IEnumerable') hace que este casi imposible, pero me imagino que esto es un uso común -caso, quizás alguien haya encontrado una solución factible después de todo. –

+0

Puede usar IEnumerable <> junto con ElementAt(), aunque la sintaxis no será tan bonita. –

Respuesta

44

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

Ambos List<T> y ReadOnlyCollection<T> (a través de List<T>.AsReadOnly()) implementan ambos.


Sólo puede ser covariantes si sólo tiene un indexador get, es decir

public T this[int index] { get; } 

Pero todos los principales colecciones tienen {get;set;}, lo que hace que incómoda. No estoy al tanto de cualquier que no sería suficiente, pero se puede envolver ella, es decir, escribir un método de extensión:

var covariant = list.AsCovariant(); 

que es una envoltura alrededor de un IList<T> que sólo expone la IEnumerable<T> y la get indexador. ..? debe ser sólo unos pocos minutos de trabajo ...

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 - ver la actualización –

+0

Gracias. Este parece ser el mejor camino a seguir. –

+0

A veces he deseado que Microsoft haya logrado una forma de heredar IList de las interfaces secundarias IReadableByIndex (que llama IIndexedEnumerable), IWritableByIndex e iAppendable, para permitir una covarianza y una contradicción útiles. Desafortunadamente, todavía no hay forma de que una propiedad de lectura y escritura pueda implementar una propiedad de solo lectura o escritura. Si eso pudiera hacerse, todos los miembros de las subinterfaces covariantes/contravariantes serían implementadas naturalmente por cualquier implementación válida de IList, de modo que las subinterfaces podrían agregarse sin romper el código existente. – supercat

2

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

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

Usted, por supuesto, volar de forma espectacular en tiempo de ejecución si se intenta poner un Tiger en la matriz en cualquier lugar.

6

Aquí es una clase que escribí para hacer frente a este escenario:

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

Ahora usted puede escribir código como este:

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

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

Obviamente, si agrega cualquier cosa menos Perros a animalList, arrojará una excepción, pero esto satisfizo mis necesidades.

+0

No necesita crear un nuevo adaptador para esto. Solo crea una nueva lista. así: nueva lista (perros) y todo va a funcionar bien. Aunque puedes, esto no resolvería el problema de la contradicción. –

+0

Es polimorfismo, incluso se ha agregado un objeto, diferente como Animal. este enlace https://msdn.microsoft.com/en-us/library/ee207183.aspx describe sobre covarianza y contravarianza – natnael88

3

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 como esto:

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