在这个非常明显的情况下,C#无法推断出类型参数:
public void Test<T>(IEnumerable<KeyValuePair<string, T>> kvp)
{
Console.WriteLine(kvp.GetType().Name + ": KeyValues");
}
Test(new Newtonsoft.Json.Linq.JObject());
JObject
类型明确实现IEnumerable<KeyValuePair<string, JToken>>
,但我收到以下错误:
CS0411: The type arguments for method cannot be inferred from the usage.
为什么会这样?
UPD :对于将此问题标记为重复的编辑:请注意我的方法的签名不是IEnumerable<T>
,而是IEnumerable<KeyValuePair<string, T>>
。 JObject
类型实现IEnumerable
两次,但只有一个实现匹配此约束 - 所以不应该有歧义。
UPD :这是一个没有JObject
的完整自包含repro:
https://gist.github.com/impworks/2eee2cd0364815ab8245b81963934642
答案 0 :(得分:13)
这是一个更简单的复制品:
interface I<T> {}
class X<T> {}
class Y {}
class Z {}
class C : I<X<Y>>, I<Z> {}
public class P
{
static void M<T>(I<X<T>> i) { }
public static void Main()
{
M(new C());
}
}
类型推断失败。你问为什么,为什么问题总是难以回答,所以让我重新解释一下这个问题:
规范的哪一行不允许这种推断?
我手头有C#3规范的副本;这条线如下
V
是我们推断到的类型,因此在这种情况下I<X<T>>
U
是我们从推断的类型,因此在这种情况下C
这在C#4中略有不同,因为我添加了协方差,但为了讨论的目的,我们可以忽略它。
...如果
V
是构造类型C<V1, … Vk>
,并且有一组唯一类型U1, … Uk
,则隐式转换从U
到{{1}存在然后,从每个C<U1, … Uk>
到相应的Ui
进行精确推断。否则没有推论。
请注意唯一字样。没有一组唯一类型,Vi
可转换为C
,因为I<Something>
和X<Y>
都有效。
这是另一个非原因问题:
设计团队做出此决定时考虑了哪些因素?
你是对的,理论上我们可以在你的情况下发现Z
是有意的而X<Y>
不是。如果您愿意提出一种类型推断算法,它可以处理这种永远不会出错的非独特情况 - 请记住,Z
可以是Z
或{{1}的子类型或超类型}}和X<Y>
可能是协变的 - 然后我确信C#团队会很乐意考虑你的提议。
我们在2005年设计了C#3类型的推理算法并确定了一个类实现两个相同接口的场景很少,并且处理这些罕见的情况会导致该语言出现相当大的复杂情况。这些复杂因素在设计,指定,实施和测试方面都很昂贵,而且我们还有其他需要花钱和努力才能产生更大影响的事情。
此外,我们不知道我们何时制作C#3是否会在C#4中添加协方差。我们绝不想引入一种新的语言功能,使未来可能的语言功能变得不可能或困难。现在最好在语言中加入限制,并考虑在以后删除它们,而不是为一个罕见的场景做很多工作,这会使下一版本中的常见场景变得困难。
我帮助设计这个算法并多次实现它,并且最初完全不记得这个规则的事实应该告诉你在过去的13年里这种情况经常出现的频率。几乎没有。有人在您的特定船上很少见,并且有一个简单的解决方法:指定类型。
你想到的一个问题但想到了:
错误信息可能更好吗?
是的。我为此道歉。我做了很多工作,使得重载解析错误消息对于常见的LINQ场景更具描述性,并且我一直希望返回并使其他类型的推理错误消息更加清晰。我编写了类型推断和重载解析代码,以维护内部信息,解释为什么推断了类型或者选择或拒绝了重载,这既是为了我自己的调试目的,也是为了制作更好的错误消息,但我从来没有暴露过给用户的信息。总有一些优先事项。
您可以考虑在Roslyn GitHub网站上输入一个问题,建议改进此错误消息,以帮助用户更轻松地诊断情况。同样,我没有立即诊断问题并且不得不回到规范的事实表明该消息不清楚。
答案 1 :(得分:0)
问题如下。
如果你没有指定类型,那么它会尝试自动推断,但是如果它混淆了那么它会抛出指定的异常。
在您的情况下,JObject实现了接口IEnumerable<KeyValuePair<string, JToken>>
。
JObject也实现了JContainer,它还实现了IEnumerable< JToken>
。
因此,当为T指定时,它会在IEnumerable<JToken>
和IEnumrable<KeyValuePair<string, JToken>>
之间混淆。
答案 2 :(得分:0)
我怀疑当设计C#的人考虑编译器如何推断泛型类型参数的类型时,他们会考虑潜在的问题。
考虑以下情况:
class MyClass : JObject, IEnumerable<KeyValuePair<string, int>>
{
public IEnumerator<KeyValuePair<string, int>> GetEnumerator()
{
throw new NotImplementedException();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
class Foo : MyClass { }
class FooBar : Foo { }
public void Test<T>(IEnumerable<KeyValuePair<string, T>> kvp)
{
Console.WriteLine(kvp.GetType().Name + ": KeyValues");
}
var fooBar = new FooBar();
Test(fooBar);
T
应推断出哪种类型? int
或JToken
?
算法应该有多复杂?
据推测,这就是为什么编译器只在没有模糊性的情况下才推断出类型。它是这样设计的,并非没有理由。