为什么C#编译器不能推断返回值的类型参数?

10

我有这段代码(为了清晰起见进行了最小化):

interface IEither<out TL, out TR> {
}

class Left<TL, TR> : IEither<TL, TR> {
    public Left(TL value) { }
}

static class Either {
    public static IEither<TL, TR> Left<TL, TR> (this TL left) {
        return new Left<TL, TR> (left);
    }
}

为什么我不能说:
static class Foo
{
    public static IEither<string, int> Bar ()
    {
        //return "Hello".Left (); // Doesn't compile
        return "Hello".Left<string, int> (); // Compiles
    }
}

我收到一个错误提示,指出'string'不包含名为'Left'的定义,并且找不到接受类型为'string'的第一个参数的扩展方法'Left'(是否缺少使用指令或程序集引用?)(CS1061)


1
不确定FooBar是什么。你能发一个完整的可重现的示例吗? - Sriram Sakthivel
@SriramSakthivel 添加了 MWE - miniBill
@Alex,那么为什么它上面的行编译通过了? - miniBill
你列出了参数,所以它可以编译。 - Alex
@Mathew 因为没有其他选择。 - miniBill
显示剩余2条评论
3个回答

6
return "Hello".Left<string, int> (); // Compiles

没有意外。您明确指定了类型参数,编译器很满意。
return "Hello".Left (); // Doesn't compile

毫不奇怪,编译器在这种情况下无法确定TR是什么。可以推断出TL,因为它作为参数left传递了,但TR则不能。

C#编译器不会对你的意图进行假设,如果意图不清楚,它会抛出编译器错误。如果编译器认为你可能做错了什么,它会给出编译器警告。


4
仅当返回值可接受时,“TR”的唯一可能取值是“int”。 “TR”本身没有此限制。 - Alex
1
@miniBill 到底编译器如何推断它呢?推断不是基于返回值的。你可以简单地写 "Hello".Left(); 而不必分配结果或返回它。在这种情况下,编译器应该做什么?编译器对 TR 应该推断什么? - Sriram Sakthivel
return tree => tree.DoSomethingWith(arg) *作为方法中唯一的语句是可以正常工作的,编译器非常愉快地推断出了您对于tree类型的意图,尽管您没有将其分配给一个本地变量。 它实际上确实会到方法的声明处,检查返回类型(即使它是泛型委托),并用它执行类型推断。 所以OP所问的远非闻所未闻。 - Theodoros Chatzigiannakis
@TheodorosChatzigiannakis 给我一个完整的推测示例。我不知道什么是“树”,什么是“DoSomethingWith”,什么是“arg”。 - Sriram Sakthivel
1
@SriramSakthivel 不,我不是说你错了。事实上,你是绝对正确的 - 这些只适用于委托。我只是想说,既然它们适用于语言中的某些东西,那么OP希望的(假设的)功能并不是真正不可能的,也不是编译器做出假设的问题。编译器中确实有一种机制可以向后遍历AST(或类似的操作),执行更复杂的类型推断(而不是假设)。只是他们决定将其保留仅用于特定情况,我认为值得一提。 - Theodoros Chatzigiannakis
显示剩余5条评论

5
我建议你查看 C# 规范中的第7.5.2节。

7.5.2 类型推断

当调用泛型方法时未指定类型参数时,会尝试进行一种类型推断过程来为调用推断类型参数。 类型推断的存在使得可以使用更方便的语法来调用泛型方法,并允许程序员避免指定冗余的类型信息。

[...]

类型推断发生在方法调用的绑定时间处理的一部分(§7.6.5.1)中,在调用的重载解析步骤之前进行[...]。

换句话说,在进行任何重载解析之前就已经进行了解析。问题是按照你所说的方式推断类型甚至没有尝试过,而且实际上也并不总是可行的。
只有在调用中的参数才会对泛型类型的参数进行类型解析!在你的示例中只有一个string!它无法从参数中推断出int,只能从调用上下文中推断出,而这在泛型类型解析时还没有解决。

所以问题就在于“我们没有以一种允许那种推断的方式来做”吗? - miniBill
没错。我会说这不是故意的,只是类型解析在重载决策方面实现的副作用。 - flindeberg

1
免责声明:本答案涉及猜测。
关于TR的信息在于它被用作return的子表达式,而return又可以与IEither<Foo, Bar>匹配,从而产生TRBar的信息。
但是有一个问题。当编译器有一个抽象语法树时,从根节点开始推断表达式类型、解决重载等,逐步向树的叶子移动会更容易。这是最容易做到的事情,也是经常做的事情。
你的情况要求编译器完全倒推 - 从方法调用到构造函数调用到return的子表达式,然后咨询当前方法的声明(并找出正确的组合为<string,int>,因为其他任何组合都不可能编译)。乍一看,这很难实现。
但在C#中有一些先例:您可以创建一个(高阶)函数,它只返回一个lambda,类型推断将基于声明工作。当在本地变量中声明lambda时,这种情况也适用(您无法将它们分配给var并要求编译器从范围内稍后使用的参数类型进行推断)。那么,既然他们已经使用lambda实现了这个功能,为什么他们没有在您描述的情况下实现它呢?考虑到他们使用lambda做到了这一点,为什么没有实现呢? 1.他们必须使用lambda:lambda表达式的整个重点是它们不应该看起来像一个大问题-它们应该尽可能容易编写(以便快速表达过滤标准、排序标准等)。这具有价值。2.即使这种情况适用于lambda,它也远非完美-事实上,它有时会表现出非常奇怪的行为。因此,设计师可能认为这是一种必要的恶,不应扩展到语言的其他部分。3.你的情况更加罕见,解决方法很容易。4.可能只是一个“还没有人实现它”。

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