2012-10-11 13 views
10

tengo el siguiente método en una clase externaIDictionary <,> contravariancia?

public static void DoStuffWithAnimals(IDictionary<string, Animal> animals) 

En mi código de llamada, ya tengo un objeto Dictionary<string, Lion>, pero no puedo dejar pasar esto en el argumento de que este método. Entonces, ¿IDictionary<,> no es contravariante? No veo ninguna razón por la cual esto no debería funcionar.

La única solución que se me ocurre es:

var animals = new Dictionary<string, Animal>(); 

foreach(var kvp in lions) { 
    animals.Add(kvp.Key, kvp.Value); 
} 

¿No hay manera de pasar este diccionario en este método, sin tener que crear un nuevo diccionario de los mismos objetos?


EDIT:

Como es mi método, sé que el único miembro que estoy usando desde el diccionario es el captador de TValue this[TKey key], que es miembro de IDictionary<TKey, TValue>, por lo que en este escenario , No puedo usar un tipo "más amplio" para el parámetro.

+2

IDictionary parece ser invariante. Su declaración no contiene detalles de entrada/salida. –

Respuesta

5

solución sabia que podría hacer algo como esto, sólo tiene que pasar en un descriptor de acceso en su lugar:

public static void DoStuffWithAnimals(Func<string, Animal> getAnimal) 
    { 
    } 

    var dicLions = new Dictionary<string, Lion>(); 
    DoStuffWithAnimals(s => dicLions[s]); 

Obviamente esto es probable que sea un poco simple para sus necesidades, pero Si solo necesitas un par de los métodos del diccionario, es bastante fácil hacerlo funcionar.

Ésta es otra forma que le da un poco de reutilización de código entre los animales:

public class Accessor<T> : IAnimalAccessor where T : Animal 
    { 
     private readonly Dictionary<string, T> _dict; 

     public Accessor(Dictionary<string, T> dict) 
     { 
      _dict = dict; 
     } 

     public Animal GetItem(String key) 
     { 
      return _dict[key]; 
     } 
    } 

    public interface IAnimalAccessor 
    { 
     Animal GetItem(string key); 
    } 

    public static void DoStuffWithAnimals(IAnimalAccessor getAnimal) 
    { 
    } 

    var dicLions = new Dictionary<string, Lion>(); 
    var accessor = new Accessor<Lion>(dicLions); 
    DoStuffWithAnimals(accessor); 
+0

Esa primera está bien para mi caso en realidad, eso es exactamente lo que necesito :) @ La respuesta de Laurence también funcionaría en mi caso también. – Connell

4

Supongamos que Derived es un subtipo de Base. Entonces, Dictionary<Base> no puede ser un subtipo de Dictionary<Derived> porque no se puede poner cualquier objeto de tipo Base en un Dictionary<Derived>, pero Dictionary<Derived> no puede ser un subtipo de Dictionary<Base> porque si se obtiene un objeto fuera de un Dictionary<Base>, no podría ser un Derived. Por lo tanto, Dictionary no es ni covariante en su parámetro de tipo.

En general, las colecciones para las que puede escribir son invariables por este motivo. Si tienes una colección inmutable, entonces podría ser covariante. (Si tuviera algún tipo de colección de solo escritura, podría ser contravariante.)

EDITAR: Y si tuviera una "colección" de la que no podría obtener datos ni incluir datos, entonces podría ser tanto co- y contravariante. (Sería también, probablemente, será inútil.)

+2

Por ejemplo, 'DoStuffWithAnimals' tiene permitido agregar' Puppy' a 'animals', pero eso debería ser ilegal si' animals' es una colección de 'Lion' (a menos que los leones estén hambrientos). – Brian

+0

¡Ah sí, por supuesto! Entonces, en mi caso, es de solo lectura y el método no agregará cachorros con mis leones. El único miembro que utilizará es el getter de 'TValue this [tecla TKey]' (que es miembro de 'IDictionary '. ¿Todavía no hay una solución? – Connell

2

Se podría modificar public static void DoStuffWithAnimals(IDictionary<string, Animal> animals) añadir una restricción genérica. Aquí es algo que funciona para mí en LINQPad:

void Main() 
{ 
    var lions = new Dictionary<string, Lion>(); 
    lions.Add("one", new Lion{Name="Ben"}); 
    AnimalManipulator.DoStuffWithAnimals(lions); 
} 

public class AnimalManipulator 
{ 
    public static void DoStuffWithAnimals<T>(IDictionary<string, T> animals) 
    where T : Animal 
    { 
     foreach (var kvp in animals) 
     { 
      kvp.Value.MakeNoise(); 
     } 
    } 
} 

public class Animal 
{ 
    public string Name {get;set;} 
    public virtual string MakeNoise() 
    { 
     return "?"; 
    } 
} 

public class Lion : Animal 
{ 
    public override string MakeNoise() 
    { 
     return "Roar"; 
    } 
} 
+0

Brillante idea! Gracias por eso. +1 – Connell

0

Si la interfaz del diccionario fue de sólo lectura (lo que le permite sólo para leer los pares de valores clave, y ni siquiera podía permitir que la capacidad de tirar de un valor por su clave), podría marcarse con el modificador de parámetro genérico out. Si la interfaz del diccionario era de solo escritura (permitiéndole insertar valores, pero no para recuperarlos o iterar sobre ellos), podría marcarse con el modificador de parámetro genérico in.

Por lo tanto, puede crear la interfaz usted mismo y ampliar la clase de diccionario para obtener la funcionalidad que necesita.

3

En primer lugar, la covarianza y la contravarianza en C# solo se aplican a las interfaces y delegados.

Así que, realmente tu pregunta está relacionada con IDictionary<TKey,TValue>.

Con eso fuera del camino, es más simple recordar que una interfaz solo puede ser co/contravariante si todos los valores de un parámetro de tipo solo se transfieren o solo se pasan.

Por ejemplo (covarianza):

interface IReceiver<in T> // note 'in' modifier 
{ 
    void Add(T item); 
    void Remove(T item); 
} 

Y (contravarianza):

interface IGiver<out T> // note 'out' modifier 
{ 
    T Get(int index); 
    T RemoveAt(int index); 
} 

nunca me acuerdo cuál de ellos es covariante y contravariante que es, pero en última instancia no importa , siempre y cuando recuerdes esta distinción de entrada/salida.

En el caso de IDictionary<TKey,TValue>, ambos parámetros de tipo se utilizan tanto en la capacidad de entrada como de salida, lo que significa que la interfaz no puede ser covariante o contravariante. Es invariante

Sin embargo, la clase Dictionary<TKey,TValue> implementa IEnumerable<T> que es covariante.

+0

No es relevante en este caso, pero co y contravariancia en C# también se aplica a los delegados. – svick

+0

Cheers @svick, actualizaré la respuesta. –

+0

IReadOnlyDictionary podría ser contravariante entonces? – Slugart

2
  1. Creo que lo que se quiere se llama covarianza, no contravariancia.
  2. Las clases en .Net no pueden ser co o contravariantes, solo las interfaces y los delegados pueden hacerlo.
  3. IDictionary<TKey, TValue> no puede ser covariante, porque eso le permitiría hacer algo como:

    IDictionary<string, Sheep> sheep = new Dictionary<string, Sheep>(); 
    IDictionary<string, Animal> animals = sheep; 
    animals.Add("another innocent sheep", new Wolf()); 
    
  4. No es IReadOnlyDictionary<TKey, TValue> en .Net 4.5. A primera vista, podría ser covariante (la otra nueva interfaz, IReadOnlyList<T> es covariante). Lamentablemente, no lo es, porque también implementa IEnumerable<KeyValuePair<TKey, TValue>> y KeyValuePair<TKey, TValue> no es covariante.

  5. Para evitar esto, se podía hacer que su método genérico con una restricción del parámetro de tipo:

    void DoStuffWithAnimals<T>(IDictionary<string, T> animals) where T : Animal 
    
+0

** 1. ** La contradicción se está convirtiendo de más estrecha a más amplia. ¿No es eso lo que trato de hacer? ** 2. ** ¡Vaya! Lo sabía, he cambiado la pregunta para referirme a 'IDictionary' cuando pregunté esa parte. ** 3. ** Aha, sí. Esto es lo que aprendí de las otras respuestas. ** 4. ** Bugger. – Connell

+0

Re 1., no es tan simple y creo que la explicación en Wikipedia es confusa. [Citando a Eric Lippert:] (http://blogs.msdn.com/b/ericlippert/archive/2007/10/16/covariance-and-contravariance-in-c-part-one.aspx) "Considera una operación" "Que manipula tipos. Si los resultados de la operación aplicados a cualquier 'T' y' U' siempre resultan en dos tipos 'T'' y' U'' con la misma relación que 'T' y' U', entonces se dice que la operación es "Covariante". Si la operación invierte el tamaño y la pequeñez de sus resultados, [...] entonces se dice que la operación es "contravariante". – svick

Cuestiones relacionadas