无法在方法调用中将字典转换为IDictionary

5

我很难理解这个。假设我有以下两个字典对象:

// Assume "query" is a LINQ queryable.
Dictionary<string, int> d1 = query.ToDictionary(k => k.Key, v => v.Value);
Dictionary<string, int>  d1 = query.ToDictionary(k => k.Key, v => v.Value);

以下语句产生编译时错误,即无法进行字典和IDictionary之间的隐式转换:
// Compile time error!
Tuple<IDictionary<string, int>, IDictionary<string, int>> = Tuple.Create(d1, d2);

我必须明确地进行转换:

Tuple<IDictionary<string, int>, IDictionary<string, int>> = Tuple.Create(d1 as IDictionary<string, int>, d2 as IDictionary<string, int>);

我不理解为什么编译器无法推断出协变操作 - 字典实现了IDictionary - 特别是因为像这样的代码当然可以正常工作,我们都知道:

IDictionary<string, int> d3 = d1;

我相信这种行为有充分的理由,我很好奇它是什么。
更新1: 只是想澄清一下,我好奇的是这种行为,而不是如何解决问题。我已经了解了不同的解决方案 :)
更新2: 感谢大家提供的精彩答案。我不知道元组是逆变的,现在我知道了。
5个回答

7
基本上,问题在于 Tuple 类族在其类型参数中不是协变的。它不能这样做,因为它是一个类。但是,可以创建接口或委托版本,这些版本将是协变的,因为没有成员接受输入位置的类型参数。
这在使用Tuple<T1>最容易看到:
Tuple<string> stringTuple = Tuple.Create("Foo");
Tuple<object> objectTuple = stringTuple;

这个调用:

Tuple.Create(d1, d2);

... 推断出两个类型参数都是 Dictionary<string, int>,所以你试图将 Tuple<Dictionary<string, int>, Dictionary<string, int>> 转换为 Tuple<IDictionary<string, int>, IDictionary<string, int>>,这是不可行的。

使用 as 的版本更改了参数类型,以便类型推断给出所需的类型参数 - 但是更简单的方法是直接编写所需的类型参数,完全避免推断,如 Sebastian 的答案所示:

Tuple.Create<IDictionary<string, int>, IDictionary<string, int>>(d1, d2)

如果你在这个点使用var,也不会太糟糕:

var x = Tuple.Create<IDictionary<string, int>, IDictionary<string, int>>(d1, d2);
x的类型现在将是Tuple<IDictionary<string, int>, IDictionary<string, int>>,这正是你想要的。
编辑:正如评论中所指出的那样,此时您可以直接使用构造函数:
var x = new Tuple<IDictionary<string, int>, IDictionary<string, int>>(d1, d2);

感谢您提供详细的答案。所有的回答都很好,但是我选择标记这个作为答案是因为它提供了更多的细节。 - 9ee1
当你需要显式指定类型参数 T1T2 给静态的 Tuple.Create<T1, T2> 方法时(就像这里一样),你也可以使用泛型类型 Tuple<T1, T2> 的普通实例构造函数和新对象表达式:new Tuple<Something, SomethingElse>(d1, d2) - Jeppe Stig Nielsen

3

You are trying to assign...

Tuple<Dictionary<string, int>, Dictionary<string, int>>

...to...

Tuple<IDictionary<string, int>, IDictionary<string, int>>

然而,Tuple<T1, T2>是一个类,因此不变


2

Tuple.Create的类型参数是从类型中推断出来的,因此右侧是两个字典的元组,这是与IDictionary的元组不同的类型 - 如果您显式将类型参数添加到Tuple.Create以使其成为IDictionary类型,那么一切都应该按预期工作,因为方法参数可以是更具体的类型。

 Tuple.Create<IDictionary<string, int>,IDictionary<string, int>>(d1,d2)

2

在C#中,Tuple<Dictionary<string, int>, Dictionary<string, int>>Tuple<IDictionary<string, int>, IDictionary<string, int>>之间不存在转换,因为C#中的类(包括Tuple<T1, T2>类)不支持协变。

编译器已经推断出您调用Tuple.Create方法返回类型的最具体类型,并且在推断过程中不使用左侧的类型。


0

尝试将变量类型从Dictionary<string, int>更改为IDictionary<string, int>

IDictionary<string, int> d1 = query.ToDictionary(k => k.Key, v => v.Value);
IDictionary<string, int>  d1 = query.ToDictionary(k => k.Key, v => v.Value);

那样做是可行的。但我对其行为很好奇,因为如果我使用隐式类型变量声明,例如 var d1 = query.ToDictionary(k => k.Key, v => v.Value); 它就不起作用。我想问题是,为什么我必须手动进行转换呢? - 9ee1
通过使用var,编译器将把类型设置为Dictionary,因为Enumerable.ToDictionary返回的是Dictionary而不是IDictionary。 - Panos Rontogiannis
你是正确的。我打错了我的意思。我想问的是为什么传递 query.ToDictionary(k => k.Key, v => v.Value) 不起作用。谢谢你的回答。 - 9ee1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接