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 года спустя):
Там будет IReadOnlyDictionary<TKey, TValue>
в.NET 4.5, но он также не будет ковариантным :·/
потому что это происходит от IEnumerable<KeyValuePair<TKey, TValue>>
, а также KeyValuePair<TKey, TValue>
не является интерфейсом и, следовательно, не может быть ковариантным.
Команде BCL придется многое переделать, чтобы придумать и использовать ICovariantPair<TKey, TValue>
вместо. Также строго типизированные индексаторы а-ля this[TKey key]
не возможны для ковариантных интерфейсов. Подобный конец может быть достигнут только путем размещения метода расширения GetValue<>(this IReadOnlyDictionary<TKey, TValue> self, TKey key)
где-то, что как-то внутренне должно вызывать реальную реализацию, что, возможно, выглядит довольно грязным подходом.
6 ответов
Это особенность. .NET 4.0 поддерживает только безопасную ковариацию. Упоминание, которое вы упомянули, потенциально опасно, так как вы можете добавить нестроковый элемент в словарь, если это возможно:
IDictionary<string, object> myDict = new Dictionary<string, string>();
myDict["hello"] = 5; // not an string
С другой стороны, IEnumerable<T>
интерфейс только для чтения T
параметр типа находится только в его выходных позициях (тип возвращаемого значения 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 поддерживает только ковариацию, а не. Он работает с IEnumerable, потому что IEnumerable доступен только для чтения.
Предполагая, что вам нужны только определенные операции из Словаря, вы можете создать оболочку:
class Container
{
private IDictionary<string, string> myDict = new Dictionary<string, string>();
public object GetByKey(string key) => myDict[key];
}
или даже реализовать нужный интерфейс в контейнере.
Обход для конкретного типа полезной ковариации на 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;
}
}