为什么 C# 编译器无法推断返回值的类型参数
本文关键字:返回值 类型参数 编译器 为什么 | 更新日期: 2023-09-27 18:26:15
我有这段代码(为清楚起见,最小化(:
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' does not contain a definition for 'Left' and no extension method 'Left' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?) (CS1061)
.
return "Hello".Left<string, int> (); // Compiles
不足为奇。您显式声明了类型参数,编译器很高兴。
return "Hello".Left (); // Doesn't compile
这也不足为奇,编译器无法找出在这种情况下TR
什么。 可以推断TL
,因为TL
作为参数传递left
但TR
不能。
C# 编译器不会对您的意思做出任何假设,如果意图不明确,它将抛出编译器错误。如果编译器认为您可能做错了什么,它会给出编译器警告。
我建议您查看C#
规范中的第7.5.2节。
7.5.2 类型推断
当调用泛型方法而不指定类型参数时,a 类型推断过程尝试推断调用的类型参数。 类型推断的存在允许更方便的语法 用于调用泛型方法,并允许程序员避免 指定冗余类型信息。
[...]
类型推断作为方法调用 (§7.6.5.1( 的绑定时处理的一部分发生,发生在调用的重载解决步骤之前 [...]
即分辨率发生在任何类型的过载解决方案完成之前,问题是甚至没有尝试过以您所说的方式推断类型,坦率地说,也并不总是可能的。
泛型类型参数的类型解析仅使用调用中的参数完成!在你的例子中只有一个string
!它不能从参数推断int
,只能推断在泛型类型解析时未解析的调用上下文。
免责声明:这个答案涉及猜测。
关于TR
的信息在于它被用作return
的子表达式,而又可以与IEither<Foo, Bar>
匹配,以产生TR
Bar
的信息。
但是有一个问题。当编译器具有抽象语法树时,更容易从根开始,通过逐步向树的叶子移动来推断表达式类型、解决重载等。这是最容易做的事情,也是更频繁的事情。
您的方案要求编译器完全向后工作 - 从方法调用到构造函数调用,再到return
的子表达式,然后查阅当前方法的声明(并找出正确的组合是<string, int>
,因为任何其他组合都不可能编译(。乍一看,这很难实现。
但是在 C# 中有一些优先顺序:您可以创建一个(高阶(函数,该函数仅返回 lambda,并且类型推断将基于声明工作。在声明局部变量时,这种事情也适用于 lambda(您不能将它们分配给 var
并要求编译器从范围中的后续用法推断参数类型(。
那么,考虑到他们用lambdas做到了,他们为什么不针对您所描述的情况实现它呢?
- 他们必须使用 lambda 来做到这一点:lambda 表达式的全部意义在于它们看起来没什么大不了的——它们应该尽可能容易编写(以便快速表达过滤标准、排序标准等(。这是有价值的。
- 尽管这种东西适用于 lambdas,但它远非完美——事实上,它有时会以非常奇怪的方式表现。因此,设计师可能会将其视为一种必要的邪恶,不应该扩展到语言的其余部分。
- 您的情况很少见,解决方法也很容易。
- 这可能只是">还没有人实施它"的情况。