C# 实例化IDictionary<;K、 IEnumerable<;V>&燃气轮机;

C# 实例化IDictionary<;K、 IEnumerable<;V>&燃气轮机;,c#,.net,covariance,C#,.net,Covariance,我有一个带签名的方法 public void Foo(IDictionary<string, IEnumerable<string>> data) { } 产生编译器错误: 参数1:无法从System.Collections.Generic.Dictionary转换为System.Collections.Generic.IDictionary Dictionary实现IDictionary和HashSet实现IEnumerable 为什么编译器无法执行转换 如何创建可

我有一个带签名的方法

public void Foo(IDictionary<string, IEnumerable<string>> data)
{
}
产生编译器错误:

参数1:无法从
System.Collections.Generic.Dictionary
转换为
System.Collections.Generic.IDictionary

Dictionary
实现
IDictionary
HashSet
实现
IEnumerable

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

如果我把签名改成

public void Foo(IDictionary<string, HashSet<string>> data)
公共void Foo(IDictionary数据)
编译成功。但是,我不需要知道正在传入一个具体类型,例如
HashSet
。任何
IEnumerable
都可以

更新

汇编如下:

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);
}
公共void Foo(IDictionary数据)
{
列表项=新列表(){“foo”、“bar”、“baz”};
数据。添加(“关键”,项目);
HashSet item2=新的HashSet(){“quu”};
数据。添加(“键2”,第2项);
}

很明显,
数据
可以接受所有实现的混合类型值
IEnumerable

这不起作用的原因是由于有关的限制。解释这一点的最好方法是举例说明为什么这一点必须失败

假设
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");
因为实现了
IEnumerable
,所以将列表对象添加到存储
IEnumerable
s的字典中绝对有效。同样:上面是签名的
Foo
的有效实现

但是,如果我们现在将这两个代码段组合起来,它就会分离:存储在键
“foo”
处的对象不是
HashSet
,而是
列表。因此,在
HashSet item=myInput[“foo”]
处的赋值将失败。但这里有一个冲突!类型系统应确保无论在
Foo
中发生什么,赋值都有效;但是
Foo
的实现对于其签名也是完全有效的


因此,这里不允许制定一些武断和不宽容的规则。类型系统只是防止使用不兼容的参数调用
Foo
。不,因为IDictionary是不变的,所以不可能绕过此限制。

根据我的评论,以下是在LINQ的帮助下使您的呼叫正常工作的方法:

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

@poke的回答告诉了你为什么,所以我不会花太多时间在这个问题上

您可以通过使用带有
其中
限制的通用方法来管理此限制,如下所示:

public void Foo<T>(IDictionary<string, T> data)
    where T : IEnumerable<string>
{
}
下面是一个快速测试:

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);
var a=newdictionary();
var b=新字典();
var c=新字典();
傅(甲),;
傅(乙);;
傅(丙),;
编译并运行良好。所有这三个词典都以包含字符串
bar
的适当实体类型的新集合结束


您不能做的是将错误类型的集合添加到
词典
。曾经您尝试对对象执行的任何操作都必须与该对象的实体类型一致。这就是为什么在这种情况下,
Foo
方法需要是通用的。

@poke:我以前读过,但不明白它在这种情况下是如何应用的。答案可能就在那里的某个地方(问题被标记为协方差)。这里的问题在于
IDictionary
接口。它的两个类型参数都不是协变的。这样就避免了任何不属于那种类型的东西被分配。好的,那么第2部分-如何实例化可以传递给
Foo
的数据呢?或者这基本上是不可能的?看看我的编辑。事实证明,您可以将
列表
哈希集
添加到
i词典数据
是的,
Foo
的实现不是问题。我上面的实现是有效的,就像您在问题中的实现是有效的一样。但是将更具体的类型传递给函数将失败,因为它的原始类型无法再强制执行(请参见我的编辑以获取示例)。关于编辑
,因为这样的情况将是这样的
-我看不到这一点<代码>数据
的值仅实现
IEnumerable
。您必须尝试在
HashSet item=myInput[“key”]中强制转换到
HashSet
。对于
列表
,强制转换将失败。但是类型系统向我保证它可以工作。如果我有一个存储哈希集的字典,并且字典发生了任何事情(在
Foo
),那么我仍然可以从字典中获取类型为
HashSet
的项。这与Foo内部发生的事情无关。虽然这允许您调用
Foo
,并将
myInput
的内容转换为适当的格式,
Foo
所需的副作用现在将作用于临时
词典
,而不是
myInput
本身。这肯定回答了问题的第2部分。
Foo(myInput.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.AsEnumerable()));
public void Foo<T>(IDictionary<string, T> data)
    where T : 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);