我有一个带签名的方法
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>
答案 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()));