鉴于:
static TDest Gimme(TSource source) { return default(TDest); }
为什么我不能这样做:
string dest = Gimme(5);
没有得到编译器错误:
error CS0411: The type arguments for method 'Whatever.Gimme
该5
可以推断为int
,但有编译器将不会/无法解决的返回类型的限制string
.我在几个地方读到这是设计但没有真正的解释.我在某处读过这可能会在C#4中发生变化,但事实并非如此.
任何人都知道为什么不能从泛型方法中推断出返回类型?这是其中一个问题,答案是如此明显,它正盯着你的脸?我希望不是!
这里的一般原则是类型信息仅从表达式的内部到外部 "单向"流动.你给出的例子非常简单.假设我们希望在对方法进行类型推断时"双向"地输入类型信息R G(A a)
,并考虑一些创建的疯狂场景:
N(G(5))
假设有十个不同的N重载,每个重载具有不同的参数类型.我们应该为R做十个不同的推论吗?如果我们这样做了,我们应该以某种方式挑选"最好的"吗?
double x = b ? G(5) : 123;
G的返回类型应该被推断为什么?Int,因为条件表达式的另一半是int?或者加倍,因为最终这个东西会分配给双倍?现在也许你开始看到这是怎么回事; 如果你要说你从外到内的理由,你走了多远?沿途可能有很多步骤.看看当我们开始结合这些时会发生什么:
N(b ? G(5) : 123)
现在我们做什么?我们有十个N的重载可供选择.我们说R是int吗?它可以是int或int可隐式转换为的任何类型.但是那些类型中哪些类型可以隐式转换为N的参数类型?我们自己编写了一个小程序,并要求prolog引擎解决R可能的所有可能的返回类型,以便满足N上的每个可能的重载,然后以某种方式选择最好的一个?
(我不是在开玩笑;有些语言本质上会编写一些prolog程序,然后使用逻辑引擎来计算出所有类型的东西.例如,F#比C#更复杂的类型推断.Haskell的类型系统实际上是Turing Complete;你可以在类型系统中对任意复杂的问题进行编码,并要求编译器解决它们.正如我们稍后将看到的,C#中的重载解析也是如此 - 你不能在C#中编码停机问题你可以在Haskell中输入类型系统,但是你可以将NP-HARD问题编码为重载解决问题.)
这仍然是一个非常简单的表达.假设你有类似的东西
N(N(b ? G(5) * G("hello") : 123));
现在我们必须多次为G解决这个问题,也可能为N解决这个问题,我们必须组合解决它们.我们有五个重载解决问题需要解决,所有这些都是公平的,应该考虑它们的参数和它们的上下文类型.如果N有十种可能性,那么N(N(...))可能需要考虑一百种可能性,而N(N(N(...))可能有一千种可能性,很快就会让我们解决容易产生数十亿种可能组合的问题并使编译器变得非常慢.
这就是为什么我们有一个规则,即类型信息只以一种方式流动.它可以防止这些类型的鸡和蛋问题,你试图从内部类型确定外部类型,并从外部类型确定内部类型,并导致组合爆炸的可能性.
请注意,类型信息确实为lambdas双向流动!如果你N(x=>x.Length)
肯定地说,我们考虑N的所有可能重载,它们的参数中包含函数或表达式类型,并尝试x的所有可能类型.当然,在某些情况下,您可以轻松地让编译器尝试数十亿种可能的组合来找到有效的独特组合.类通用规则使得通用方法可以做到这一点非常复杂,甚至让Jon Skeet感到紧张.此功能使重载分辨率NP-HARD.
获取类型信息以使lambdas双向流动,以便通用重载解析正常有效地工作了大约一年.这是一个如此复杂的特征,如果我们绝对肯定会对这笔投资有惊人的回报,我们只想接受它.使LINQ工作是值得的.但是没有像LINQ这样的相应功能可以证明一般来说这项工作需要付出巨大的代价.
你必须做:
string dest = Gimme(5);
您需要在泛型方法的调用中指定类型.怎么知道你想在输出中输入一个字符串?
System.String是一个不好的例子,因为它是一个密封的类,但是它说不是.如果您没有在调用中指定类型,编译器如何知道您不想要其中一个子类?
举个例子:
System.Windows.Forms.Control dest = Gimme(5);
编译器如何知道实际控制的是什么?您需要像这样指定它:
System.Windows.Forms.Control dest = Gimme(5);
调用Gimme(5)
忽略返回值是一个法律声明,编译器将如何知道返回哪种类型?