C#编译器是否不能根据期望的返回类型推断方法类型参数?

6
这对我来说似乎很奇怪,但我记得有一个线程,Eric Lippert在其中评论了C#无法根据返回类型重载方法的能力(至少是约定俗成),因此也许它与此有某种复杂的关系。
这个为什么不起作用呢?
public static T Test<T>() where T : new()
{
    return new T();
}

// Elsewhere
SomeObject myObj = Test();

但是这个可以:
 var myObj = Test<SomeObject>();

从某个角度看,它们都可以,因为你没有重复自己(在非常小的程度上),但这只是编译器的不同处理方式吗?


协变性/逆变性?我认为你可能需要说明你所指的C#版本。http://bit.ly/gG4Iis 编辑:我错了;C++也不能基于返回类型进行重载。毕竟,返回类型可以完全省略,因此我看不到编译器检测正确函数的可靠方法。 - Uwe Keim
我假设在谈到协变性/逆变性时,你是在解决返回类型的解析问题;正如我所想的那样,这基本上是一个转移注意力的话题,但仍然是一个值得关注的观点。 - Marc Bollinger
5个回答

5

首先,这是“基于返回类型的重载”:

void M(int x){...}
int M(int x){...}
string M(int x){...}

这些声明是不合法的;你不能基于返回类型重载方法,因为返回类型不是签名的一部分,而且签名必须是唯一的。

你所说的是基于方法返回类型的方法类型推断,我们也不支持。

原因是因为返回类型可能是你试图确定的东西。

M(Test());

Test的返回类型取决于我们选择哪个M重载。我们选择哪个M重载取决于Test的返回类型。

总的来说,C#的设计是每个子表达式都有一个类型,并且类型是从“内部”向“外部”解决的,而不是从外部向内部解决的。

显而易见的例外情况是匿名函数、方法组和null:

M(x=>x+1)
x=>x+1 的类型是什么?这取决于调用的 M 函数的哪个重载。
M(N); // N is a method group

N的类型是什么?这取决于调用M的哪个重载。

类似这样的情况,我们从“外部”到“内部”推理。

涉及lambda的类型推断非常复杂,难以实现。我们不希望在编译器中出现同样的复杂性和困难。


抱歉,我确实知道这两个概念的定义和区别,但是重新阅读后,简洁性使得“在实现动机方面有些相关”变成了“这些东西几乎相同”。我不是那个意思。我的问题更多地涉及意图,而不是语法合法性,所以你最后一段话基本上正是我想要的。 :) - Marc Bollinger

4

除了无类型表达式(null、方法组和lambda表达式)外,表达式的类型必须由表达式本身静态确定,而不考虑上下文。

换句话说,表达式Test()的类型不能取决于你要将其赋给什么。


2
条件运算符是有类型的。我认为你想表达的是“空字面值、方法组和匿名函数”。 - Eric Lippert

3

请查阅C#语言规范§7.5.2,变量的声明类型不是类型推断的证明,显然它也不应该是。考虑以下代码:

Base b = Test<Derived>();
Derived d = Test<Derived>();

由于 C# 中存在隐式转换,因此该方法的返回类型可能与变量声明类型不同。


获取检查,仅用于引用相关规范部分 :) - Marc Bollinger
1
“显然这不应该是这样的” - 为什么不呢?当它不含糊时,Java允许这样做,我认为这是Java做对了而C#做错了的极少数情况之一。 - Konrad Rudolph
@Konrad Rudolph:Java做对了,而C#做错了?请证明。在我未发布(也永远不会发布)的个人编程语言中,1+1=3,那么我可以说它是正确的,而Java/C#是错误的,因为它们的1+1=2吗?在这里我谈论的是C#中的事情。 - Cheng Chen
1
“正确”的选择当然是个人观点。C# 中的所有设计选择都是经过仔细考虑许多竞争对手的结果。在我看来,这个选择更符合 C# 的基本设计原则,而不是其他选择。至于 Java 的发明者是否有不同的设计原则,我不知道;你得问问他们为什么做出了不同的选择。 - Eric Lippert
最聪明的:当我说Java做得对时,我显然是指Java的设计决策比C#更好。有什么需要证明的呢?代码可以运行,这很有帮助,尽管不是至关重要的。显然有人持不同意见(例如Eric),但就记录而言,他的解释对我毫无影响。这是一个很好的技术解释,但它忽略了用户体验,在我看来,这才是语言设计必须迎合的。此外,还有先例(看看Eric的回答),所以即使他的技术解释并不完全正确。 - Konrad Rudolph

1
编译器的类型推断不会将赋值的“期望类型”作为逻辑的一部分。
因此,类型推断的“考虑范围”不是这个:
SomeObject myObj = Test();

但是这个:

Test();

而且,这里没有关于预期类型的线索。


1

如果你想要一个例子来说明为什么表达式的类型需要能够由表达式本身确定,可以考虑以下两种情况:

  1. 我们根本不使用返回值——我们只是为了其副作用而调用该方法。
  2. 我们直接将返回值传递到重载方法中。

在泛型类型解析方面使用返回值的“期望类型”会给编译器带来很多额外的复杂性,而你所获得的只是有时需要显式指定类型,有时不需要,而且是否需要取决于代码中其他地方的无关变化。


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