IDictionary in.NET 4 не ковариантный


The 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 года спустя):

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

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

5 54

5 ответов:

это особенность. .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>.

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

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

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

myDict.Add(key, value);   

и

TValue value = myDict[key];

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

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

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

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

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

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

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

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