.NET 4 中的 IDictionary<TKey, TValue> 不是协变的
Posted
技术标签:
【中文标题】.NET 4 中的 IDictionary<TKey, TValue> 不是协变的【英文标题】:IDictionary<TKey, TValue> in .NET 4 not covariant 【发布时间】:2011-01-10 02:41:52 【问题描述】:.NET 4/Silverlight 4 中的IDictionary<TKey, TValue>
不支持协方差,即我不能这样做
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>
。对于协变接口,强类型索引器 á la this[TKey key]
也是不可能的。类似的结果只能通过在某处放置扩展方法 GetValue<>(this IReadOnlyDictionary<TKey, TValue> self, TKey key)
来实现,该方法在内部必须以某种方式调用实际的实现,可以说这看起来是一种相当混乱的方法。
【问题讨论】:
感谢您提供有关 .NET 4.5 的更新。恕我直言,在只读字典上使用协方差会很有用,所以它看起来不被支持太糟糕了。 可以在here 找到支持向上转换的IReadOnlyDictionary
包装器。它可以包装字符串字典并将其公开为对象字典。
【参考方案1】:
假设您只需要字典中的特定操作,您可以创建一个包装器:
class Container
private IDictionary<string, string> myDict = new Dictionary<string, string>();
public object GetByKey(string key) => myDict[key];
甚至在Container中实现你需要的接口。
【讨论】:
【参考方案2】:在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;
【讨论】:
【参考方案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】:但是你可以说
myDict.Add("Hello, world!", new DateTime(2010, 1, 27));
这会惨遭失败。问题是IDictionary<TKey, TValue>
中的TValue
用于输入和输出位置。也就是说:
myDict.Add(key, value);
和
TValue value = myDict[key];
那是错误还是功能?
这是设计使然。
它会出现吗,也许在 .NET 37.4 中?
不,它本质上是不安全的。
【讨论】:
【参考方案5】:.NET 4 仅支持 out 协方差,不支持 in。它适用于 IEnumerable,因为 IEnumerable 是只读的。
【讨论】:
“协方差”是用词不当。这将是逆变,它在 .NET 4 中受支持,并且在某些场景中很有用。【参考方案6】:这是一个功能。 .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>
是安全的。
【讨论】:
啊,好吧,当然,我确实打算用于只读用途。 .NET 库肯定会遗漏只读 Dictionary 类型。这些天有人应该发布关于该问题的另一个问题。 ;-) 理论上协方差是安全的,但 .Net 1.0 的一个怪癖可能会在工作中产生轻微的影响。因为Derived[]
被认为继承自Base[]
,所以Derived[]
会实现IList<Base>
;这样的IList<Base>
可以正常读取,但写入时会抛出异常。以上是关于.NET 4 中的 IDictionary<TKey, TValue> 不是协变的的主要内容,如果未能解决你的问题,请参考以下文章
嵌套 IEnumerable<IDictionary> 到不同类型的嵌套 IEnumerable<IDictionary> 的映射
一:Newtonsoft.Json 支持序列化与反序列化的.net 对象类型;
序列化 IEnumerable<IDictionary<string, object>> 不适用于 DefaultNamingStrategy