.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&lt;TKey, TValue&gt; 不支持协方差,即我不能这样做

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

类似于我现在可以用IEnumerable&lt;T&gt;s 做什么。

可能归结为KeyValuePair&lt;TKey, TValue&gt; 也不是协变的。我觉得至少在字典中应该允许协方差的值。

那是错误还是功能?它会出现吗,也许在 .NET 37.4 中?

更新(2 年后):

.NET 4.5 中会有一个IReadOnlyDictionary&lt;TKey, TValue&gt;,但它也不是协变的:·/,因为它派生自IEnumerable&lt;KeyValuePair&lt;TKey, TValue&gt;&gt;,而KeyValuePair&lt;TKey, TValue&gt; 不是接口,因此不能是协变的。

BCL 团队必须重新设计很多东西才能提出并改用ICovariantPair&lt;TKey, TValue&gt;。对于协变接口,强类型索引器 á la this[TKey key] 也是不可能的。类似的结果只能通过在某处放置扩展方法 GetValue&lt;&gt;(this IReadOnlyDictionary&lt;TKey, TValue&gt; 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&lt;TKey, TValue&gt; 中的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&lt;T&gt; 是一个只读接口。 T 类型参数仅在其输出位置(Current 属性的返回类型),因此将IEnumerable&lt;string&gt; 视为IEnumerable&lt;object&gt; 是安全的。

【讨论】:

啊,好吧,当然,我确实打算用于只读用途。 .NET 库肯定会遗漏只读 Dictionary 类型。这些天有人应该发布关于该问题的另一个问题。 ;-) 理论上协方差是安全的,但 .Net 1.0 的一个怪癖可能会在工作中产生轻微的影响。因为Derived[]被认为继承自Base[],所以Derived[]会实现IList&lt;Base&gt;;这样的IList&lt;Base&gt; 可以正常读取,但写入时会抛出异常。

以上是关于.NET 4 中的 IDictionary<TKey, TValue> 不是协变的的主要内容,如果未能解决你的问题,请参考以下文章

嵌套 IEnumerable<IDictionary> 到不同类型的嵌套 IEnumerable<IDictionary> 的映射

一:Newtonsoft.Json 支持序列化与反序列化的.net 对象类型;

序列化 IEnumerable<IDictionary<string, object>> 不适用于 DefaultNamingStrategy

.Net Hashtable - Contains vs ContainsKey

Json.NET:反序列化嵌套字典

如何遍历“IDictionary”的键和值?