IDictionary <TKey, TValue> в .NET 4 не ковариант

IDictionary<TKey, TValue> в .NET 4/Silverlight 4 не поддерживает ковариацию, т.е. я не могу сделать

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

аналог того, что я могу сделать с IEnumerable<T> сейчас.

Вероятно, сводится к тому, что KeyValuePair<TKey, TValue> не является ковариантным. Я считаю, что ковариация должна допускаться в словарях, по крайней мере, для значений.

Так что это ошибка или функция? Придет ли это когда-нибудь, возможно, в .NET 37.4?

ОБНОВЛЕНИЕ (2 года спустя):

В .NET 4.5 будет IReadOnlyDictionary<TKey, TValue>, но он не будет ковариантным либо :·/, потому что он происходит из IEnumerable<KeyValuePair<TKey, TValue>>, а KeyValuePair<TKey, TValue> не является интерфейсом и, следовательно, не может быть ковариантным.

Команда BCL должна будет перепроектировать много, чтобы подойти и использовать вместо нее ICovariantPair<TKey, TValue>. Кроме того, ковариантные интерфейсы не могут быть введены строго типизированными индексаторами á la this[TKey key]. Подобный конец может быть достигнут только путем размещения метода расширения GetValue<>(this IReadOnlyDictionary<TKey, TValue> self, TKey key) где-то, что бы как-то внутренне требовало фактической реализации, которая, по-видимому, выглядит довольно беспорядочным.

Ответы

Ответ 1

Это особенность..NET 4.0 поддерживает только безопасную ковариацию. Вы упомянули о потенциально опасном, поскольку вы могли бы добавить в словарь нестроковый элемент, если это возможно:

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

С другой стороны, IEnumerable<T> является интерфейсом только для чтения. Параметр T type находится только в его выходных позициях (тип возврата свойства Current), поэтому безопасно обрабатывать IEnumerable<string> как IEnumerable<object>.

Ответ 2

Но тогда вы могли бы сказать

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

который потерпел бы неудачу. Проблема заключается в том, что TValue in IDictionary<TKey, TValue> используется как в позиции ввода, так и вывода. К остроумию:

myDict.Add(key, value);   

и

TValue value = myDict[key];

Итак, это ошибка или функция?

Это по дизайну.

Придет ли он когда-нибудь, возможно, в .NET 37.4?

Нет, это по своей сути небезопасно.

Ответ 3

У меня была аналогичная проблема, но с более специализированными производными типами (а не с объектом, из которого все происходит)

Трюк заключается в том, чтобы сделать метод общим и поставить предложение where, устанавливающее соответствующее ограничение. Предполагая, что вы имеете дело с базовыми типами и производными типами, следующие работы:

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

Ответ 4

.NET 4 поддерживает только ковариацию. Она работает с IEnumerable, потому что IEnumerable доступен только для чтения.

Ответ 5

Работает для определенного типа полезной ковариации на 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;
    }   
}