实例化IDictionary <k,ienumerable <v =“”>&gt; </k,>

时间:2014-04-29 00:39:41

标签: c# .net covariance

我有一个带签名的方法

public void Foo(IDictionary<string, IEnumerable<string>> data)
{
}

并希望传入

private Dictionary<string, HashSet<string>> myInput = 
    new Dictionary<string, HashSet<string>>()
{
};      

该行

Foo(myInput);

产生编译器错误:

  

参数1:无法从System.Collections.Generic.Dictionary<string,System.Collections.Generic.HashSet<string>>转换为System.Collections.Generic.IDictionary<string,System.Collections.Generic.IEnumerable<string>>

Dictionary<K,V>实施IDictionary<K,V>HashSet<T>实施IEnumerable<T>

  • 为什么编译器无法执行转换?
  • 如何创建可以传递给Foo的数据实例?

请注意

如果我将签名更改为

public void Foo(IDictionary<string, HashSet<string>> data)

编译成功。但是,我不需要知道传递HashSet<T>这样的具体类型。任何IEnumerable<T>都可以。

更新

以下编译:

public void Foo(IDictionary<string, IEnumerable<string>> data)
{
    List<string> item = new List<string>() { "foo", "bar", "baz" };
    data.Add("key", item);
    HashSet<string> item2 = new HashSet<string>() { "quu" };
    data.Add("key2", item2);
}

如此明确data可以接受所有实现IEnumerable<T>

的混合类型值

3 个答案:

答案 0 :(得分:4)

这不起作用的原因是因为covariance and contravariance in generics的限制。解释此问题的最佳方法是显示此必须失败的原因的示例

假设Foo具有有效签名,则类型系统承诺以下工作:

var myInput = new Dictionary<string, HashSet<string>>();

// assuming a valid signature for `Foo`
Foo(myInput);

// according to the type of `myInput` the following MUST work
HashSet<string> item = myInput["foo"];
item.Add("baz");

绝对必须工作。所以Foo确实必须确保这仍然有效。

暂时忽略上述内容,让我们假设Foo的以下有效实现:

public void Foo (IDictionary<string, IEnumerable<string>> data)
{
    List<string> item = new List<string>(){ "foo", "bar" };
    data.Add("foo", item);
}

因为List<string>实现了IEnumerable<string>,所以将列表对象添加到存储IEnumerable<string>的字典中绝对有效。再说一遍:上面是Foo签名的有效实现。

但是,如果我们现在合并两个代码段,它就会崩溃:存储在密钥"foo"中的对象不是HashSet<string>而是List<string>。因此HashSet<string> item = myInput["foo"]的作业将失败。但这是冲突!无论Foo内发生什么,类型系统都应该确保分配有效;但Foo的实施对其签名也完全有效。

所以不要在这里制定一些任意和不透明的规则,这是不允许的。类型系统只是阻止Foo调用不兼容的参数。不,因为IDictionary是不变的,所以不可能解决这个限制。

答案 1 :(得分:1)

@ poke的回答告诉你原因,所以我不会花太多时间在那上面。

您可以使用具有where限制的通用方法来管理此限制,如下所示:

public void Foo<T>(IDictionary<string, T> data)
    where T : IEnumerable<string>
{
}

现在,Foo方法接受实现IEnumerable<string>的任何类型的对象,无论可枚举的实体类型如何。除了添加包含内容的项目之外,您可以对字典执行任何操作。

但是,让我们说想要添加一些项目:

public void Foo<T>(IDictionary<string, T> data)
    where T : ICollection<string>, new()
{
    var col = new T();
    col.Add("bar");

    data["col"] = col;
}

这是一个快速测试:

var a = new Dictionary<string, HashSet<string>>();
var b = new Dictionary<string, List<string>>();
var c = new Dictionary<string, LinkedList<string>>();

Foo(a);
Foo(b);
Foo(c);

编译并运行正常。所有三个词典最终都包含一个包含字符串bar的适当实体类型的新集合。

您可以做的是向Dictionary添加错误类型的集合。永远。您尝试对对象执行的操作必须与该对象的实体类型一致。这就是Foo方法在这种情况下需要通用的原因。

答案 2 :(得分:0)

根据我的评论,这是让你的电话在LINQ的帮助下工作的方法:

Foo(myInput.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.AsEnumerable()));