2010-01-27 12 views
50

El IDictionary<TKey, TValue> en .NET 4/Silverlight 4 no soporta covarianza, es decir, no puedo hacer un análogoIDictionary <TKey, TValue> en .NET 4 no covariantes

IDictionary<string, object> myDict = new Dictionary<string, string>(); 

a lo que se puede hacer con IEnumerable<T> s ahora.

Probablemente se reduce al KeyValuePair<TKey, TValue> que tampoco es covariante. Creo que la covarianza debe permitirse en los diccionarios, al menos para los valores.

¿Es eso un error o una característica? ¿Alguna vez vendrá, tal vez en .NET 37.4?

UPDATE (2 años después):

Habrá un IReadOnlyDictionary<TKey, TValue> en .NET 4.5, pero no se covariantes ya sea :·/, porque deriva de IEnumerable<KeyValuePair<TKey, TValue>>, y KeyValuePair<TKey, TValue> no es una interfaz y por lo tanto no puede ser covariante

El equipo de BCL tendría que rediseñar mucho para obtener y usar algo de ICovariantPair<TKey, TValue>. Tampoco son posibles los indexadores fuertemente tipados a la this[TKey key] para las interfaces covariantes. Un fin similar solo se puede lograr colocando un método de extensión GetValue<>(this IReadOnlyDictionary<TKey, TValue> self, TKey key) en algún lugar que de alguna manera internamente tenga que llamar a una implementación real, lo que podría parecer un enfoque bastante desordenado.

+7

Gracias por proporcionar la información actualizada sobre .NET 4.5. En mi humilde opinión, sería útil tener covarianza en un diccionario de solo lectura, así que es una lástima que no parezca que será compatible. – dcstraw

Respuesta

46

Es una característica. .NET 4.0 solo es compatible con seguro covarianza. El elenco que ha mencionado es potencialmente peligroso ya que podría agregar un elemento no-cadena en el diccionario si es que eso era posible:

IDictionary<string, object> myDict = new Dictionary<string, string>(); 
myDict["hello"] = 5; // not an string 

Por otro lado, IEnumerable<T> es una interfaz de sólo lectura. El parámetro de tipo T está solo en sus posiciones de salida (tipo de retorno de la propiedad Current) por lo que es seguro tratar IEnumerable<string> como IEnumerable<object>.

+0

Ahh bien, por supuesto, de hecho tenía la intención de usar solo lectura. La biblioteca .NET seguramente omite un tipo de diccionario de solo lectura. Alguien debería publicar otra pregunta sobre ese tema uno de estos días. ;-) – herzmeister

+1

En teoría, la covarianza es segura, pero una peculiaridad de .Net 1.0 puede arrojar una ligera llave en la obra. Debido a que 'Derived []' se considera que hereda de 'Base []', un 'Derived []' implementará 'IList '; tal 'IList ' funcionará correctamente para la lectura, pero emitirá una excepción cuando se escriba en. – supercat

11

Pero entonces se podría decir

myDict.Add("Hello, world!", new DateTime(2010, 1, 27)); 

que fallan miserablemente. El problema es que el TValue en IDictionary<TKey, TValue> se usa en las posiciones de entrada y salida. A saber:

myDict.Add(key, value); 

y

TValue value = myDict[key]; 

Así es que un error o de una pieza?

Es por diseño.

¿Alguna vez vendrá, quizás en .NET 37.4?

No, es intrínsecamente inseguro.

2

.NET 4 solo es compatible con covarianza no en. Funciona con IEnumerable porque IEnumerable es de solo lectura.

+10

"en covarianza" es un nombre inapropiado. Eso sería una contradicción, y es compatible con .NET 4 y es útil en ciertos escenarios. – dcstraw

0

un trabajo alrededor para un tipo específico de covarianza útil sobre IDictionary

public static class DictionaryExtensions 
{ 
    public static IReadOnlyDictionary<TKey, IEnumerable<TValue>> ToReadOnlyDictionary<TKey, TValue>(
     this IDictionary<TKey, List<TValue>> toWrap) 
    { 
     var intermediate = toWrap.ToDictionary(a => a.Key, a => a.Value!=null ? 
             a.Value.ToArray().AsEnumerable() : null); 
     var wrapper = new ReadOnlyDictionary<TKey, IEnumerable<TValue>>(intermediate); 
     return wrapper; 
    } 
} 
5

que tenía un problema similar, pero con tipos derivados más especializados (en lugar de objeto que todo se deriva de)

El truco es hacer el método genérico y poner una cláusula Where poniendo la restricción relevante. Suponiendo que usted está tratando con tipos de bases y tipos derivados, las siguientes obras:

using System; 
using System.Collections.Generic; 

namespace GenericsTest 
{ 
class Program 
{ 
    static void Main(string[] args) 
    { 
     Program p = new Program(); 

     p.Run(); 
    } 

    private void Run() 
    { 

     Dictionary<long, SpecialType1> a = new Dictionary<long, SpecialType1> { 
     { 1, new SpecialType1 { BaseData = "hello", Special1 = 1 } }, 
     { 2, new SpecialType1 { BaseData = "goodbye", Special1 = 2 } } }; 

     Test(a); 
    } 

    void Test<Y>(Dictionary<long, Y> data) where Y : BaseType 
    { 
     foreach (BaseType x in data.Values) 
     { 
      Console.Out.WriteLine(x.BaseData); 
     } 
    } 
} 

public class BaseType 
{ 
    public string BaseData { get; set; } 
} 

public class SpecialType1 : BaseType 
{ 
    public int Special1 { get; set; } 
} 
} 
+0

¡esta es una gran solución! Me permite hacer exactamente lo que tengo que hacer para la reutilización del código, y evito por completo el problema que la covarianza introduciría. – jltrem

Cuestiones relacionadas