.NET中的generics方法不能推导出它们的返回types。 为什么?
鉴于:
static TDest Gimme<TSource,TDest>(TSource source) { return default(TDest); }
为什么我不能这样做:
string dest = Gimme(5);
没有得到编译器错误:
error CS0411: The type arguments for method 'Whatever.Gimme<TSource,TDest>(TSource)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
5
可以被推断为int
,但是在编译器不能/不能将string
parsing为返回types的地方有一个限制。 我已经在几个地方看过这个devise,但没有真正的解释。 我读过的地方可能会改变在C#4,但它没有。
任何人都知道为什么返回types不能从generics方法推断出来? 这个答案如此明显的问题就是这个问题吗? 我希望不是!
这里的一般原则是types信息只从一个expression的内部stream向“ 外部 ”。 你给的例子非常简单。 假设我们在对方法R G<A, R>(A a)
进行types推断时想要“双向的”types信息stream,并考虑一些疯狂的情景,
N(G(5))
假设有十个不同的N重载,每个都有不同的参数types。 我们应该对R进行十种不同的推论吗? 如果我们这样做,我们是否应该select“最好”的?
double x = b ? G(5) : 123;
G的返回types应该推断为什么? 诠释,因为另一半的条件expression式是int? 还是加倍,因为最终这件事情将被分配到两倍? 现在也许你开始明白这是怎么回事了。 如果你要说从外到内的理由, 你会走多远 ? 一路上可能会有很多步骤。 看看我们开始合并这些时会发生什么:
N(b ? G(5) : 123)
现在我们该怎么办? 我们有十个重载的N可供select。 我们说R是int吗? 它可以是int或任何可以隐式转换为int的types。 但是那些可以隐式转换为N的参数types的types呢? 我们是否自己编写一个prolog程序,并要求prolog引擎解决R中可能出现的所有可能的返回types,以便满足N上每个可能的重载,然后以某种方式select最好的一个?
(我不是在开玩笑,有些语言本质上是编写一个prolog程序,然后用一个逻辑引擎来确定什么types的东西,例如F#,比C#做的更复杂的types推断。系统实际上是Turing Complete;你可以在types系统中任意编码复杂的问题,并要求编译器解决它们。我们将在后面看到,C#中的重载分辨率也是如此 – 你不能在C#类似于Haskell中的系统,但是可以将NP-HARD问题编码为重载问题。)
这仍然是一个非常简单的expression。 假设你有类似的东西
N(N(b ? G(5) * G("hello") : 123));
现在我们必须多次为G解决这个问题,也可能为N解决这个问题,我们必须结合它们来解决它们。 我们有五个重载解决问题需要解决, 所有这些问题,公平的,应该考虑他们的论点和他们的语境types。 如果N有十种可能性,那么N(N(…))和N(N(N(…)))有可能有一百种可能性,而且很快你就要求我们解决这些问题很容易有数十亿种可能的组合,并使得编译器非常慢。
这就是为什么我们有这样的规则:types信息只能以一种方式stream动。 它可以防止这些types的鸡和鸡蛋的问题,你试图从内部types确定外部types,并从外部types确定内部types,并导致可能性的组合爆炸。
请注意,types信息确实对lambdaexpression式是双向的! 如果你说N(x=>x.Length)
那么就足够了,我们考虑在它们的参数中有N个函数或expression式types的所有可能的重载,并且尝试所有可能的types。 果然, 在某些情况下,您可以轻松地让编译器尝试数十亿种可能的组合,以find唯一可行的组合 。 types推理规则可以为generics方法做到这一点,这是非常复杂的,甚至让Jon Skeet感到紧张。 此function使重载解决NP-HARD 。
获取types信息以便为lambdaexpression式的双向stream动,以便generics重载parsing能够正确高效地工作了大约一年的时间。 这是一个非常复杂的特征,我们只是想把它拿下来,如果我们绝对肯定会有这个投资的惊人的回报。 让LINQ工作是值得的。 但是没有像LINQ这样的对应function,certificate了这个工作的巨大开销。
你必须做:
string dest = Gimme<int, string>(5);
您需要在调用generics方法时指定types。 怎么会知道你想在输出中input一个string?
System.String是一个不好的例子,因为它是一个密封的类,但是说不是。 如果你没有在调用中指定types,编译器如何知道你不需要它的一个子类呢?
以这个例子:
System.Windows.Forms.Control dest = Gimme(5);
编译器如何知道实际要做什么控制? 你需要像这样指定它:
System.Windows.Forms.Control dest = Gimme<int, System.Windows.Forms.Button>(5);
调用Gimme(5)
忽略返回值是一个法律声明,编译器将如何知道返回的types?
我猜想这是一个devise决定。 在使用Java进行编程时,我也觉得它很有用。
与Java不同的是,C#似乎正朝着函数式编程语言发展,您可以相反地进行types推断,所以您可以:
var dest = Gimme<int, string>(5);
这将推断dest的types。 我想混合这个和Java风格推断可能被certificate是相当困难的实现。
如果一个函数应该返回less量types之一,你可以让它返回一个定义有扩展转换类的类。 我不认为有可能以通用的方式做到这一点,因为宽泛的ctype操作符不接受genericstypes参数。