c#中的通用接口类型推断怪异

时间:2018-05-17 11:51:17

标签: c# .net generics json.net type-inference

在这个非常明显的情况下,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

3 个答案:

答案 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)

问题如下。

如果你没有指定类型,那么它会尝试自动推断,但是如果它混淆了那么它会抛出指定的异常。

  1. 在您的情况下,JObject实现了接口IEnumerable<KeyValuePair<string, JToken>>

  2. JObject也实现了JContainer,它还实现了IEnumerable< JToken>

  3. 因此,当为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应推断出哪种类型? intJToken

算法应该有多复杂?

据推测,这就是为什么编译器只在没有模糊性的情况下才推断出类型。它是这样设计的,并非没有理由。