IDictionary <TKey, TValue> в .NET 4 не ковариантен - PullRequest
57 голосов
/ 27 января 2010

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

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

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

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

Так это баг или фича? Будет ли это когда-нибудь, может быть, в .NET 37.4?

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

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

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

Ответы [ 5 ]

48 голосов
/ 27 января 2010

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

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

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

11 голосов
/ 27 января 2010

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

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

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

myDict.Add(key, value);   

и

TValue value = myDict[key];

Так это ошибка или особенность?

Это по замыслу.

Будет ли это когда-нибудь, может быть, в .NET 37.4?

Нет, это небезопасно.

5 голосов
/ 31 октября 2014

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

Хитрость заключается в том, чтобы сделать метод универсальным и поместить предложение 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; }
}
}
2 голосов
/ 27 января 2010

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

0 голосов
/ 02 апреля 2014

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