.NET中的通用方法不能推断其返回类型.为什么?

Ste*_*unn 50 .net c# type-inference

鉴于:

static TDest Gimme<TSource,TDest>(TSource source) 
{ 
    return default(TDest); 
}
Run Code Online (Sandbox Code Playgroud)

为什么我不能这样做:

string dest = Gimme(5);
Run Code Online (Sandbox Code Playgroud)

没有得到编译器错误:

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.我在几个地方读到这是设计但没有真正的解释.我在某处读过这可能会在C#4中发生变化,但事实并非如此.

任何人都知道为什么不能从泛型方法中推断出返回类型?这是其中一个问题,答案是如此明显,它正盯着你的脸?我希望不是!

Eri*_*ert 87

这里的一般原则是类型信息仅从表达式的内部外部 "单向"流动.你给出的例子非常简单.假设我们希望在对方法进行类型推断时"双向"地输入类型信息R G<A, R>(A a),并考虑一些创建的疯狂场景:

N(G(5))
Run Code Online (Sandbox Code Playgroud)

假设有十个不同的N重载,每个重载具有不同的参数类型.我们应该为R做十个不同的推论吗?如果我们这样做了,我们应该以某种方式挑选"最好的"吗?

double x = b ? G(5) : 123;
Run Code Online (Sandbox Code Playgroud)

G的返回类型应该被推断为什么?Int,因为条件表达式的另一半是int?或者加倍,因为最终这个东西会分配给双倍?现在也许你开始看到这是怎么回事; 如果你要说你从外到内的理由,你走了多远?沿途可能有很多步骤.看看当我们开始结合这些时会发生什么:

N(b ? G(5) : 123)
Run Code Online (Sandbox Code Playgroud)

现在我们做什么?我们有十个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));
Run Code Online (Sandbox Code Playgroud)

现在我们必须多次为G解决这个问题,也可能为N解决这个问题,我们必须组合解决它们.我们有五个重载解决问题需要解决,所有这些都是公平的,应该考虑它们的参数和它们的上下文类型.如果N有十种可能性,那么N(N(...))可能需要考虑一百种可能性,而N(N(N(...))可能有一千种可能性,很快就会让我们解决容易产生数十亿种可能组合的问题并使编译器变得非常慢.

这就是为什么我们有一个规则,即类型信息只以一种方式流动.它可以防止这些类型的鸡和蛋问题,你试图从内部类型确定外部类型,并从外部类型确定内部类型,并导致组合爆炸的可能性.

请注意,类型信息确实为lambdas双向流动!如果你N(x=>x.Length)肯定地说,我们考虑N的所有可能重载,它们的参数中包含函数或表达式类型,并尝试x的所有可能类型.当然,在某些情况下,您可以轻松地让编译器尝试数十亿种可能的组合来找到有效的独特组合.类通用规则使得通用方法可以做到这一点非常复杂,甚至让Jon Skeet感到紧张.此功能使重载分辨率NP-HARD.

获取类型信息以使lambdas双向流动,以便通用重载解析正常有效地工作了大约一年.这是一个如此复杂的特征,如果我们绝对肯定会对这笔投资有惊人的回报,我们只想接受它.使LINQ工作是值得的.但是没有像LINQ这样的相应功能可以证明一般来说这项工作需要付出巨大的代价.

  • 即使使用命名参数之类的东西也会很方便......用法`var返回Gimme&lt;TDest:string&gt;(5);`我想知道,因为我已经看到了几个我想为输入提供匿名类型的位置,但是能够定义输出类型。这通常以转到 `Func&lt;...&gt;` 结束。 (2认同)

Dav*_*kle 7

你必须做:

string dest = Gimme<int, string>(5);
Run Code Online (Sandbox Code Playgroud)

您需要在泛型方法的调用中指定类型.怎么知道你想在输出中输入一个字符串?

System.String是一个不好的例子,因为它是一个密封的类,但是它说不是.如果您没有在调用中指定类型,编译器如何知道您不想要其中一个子类?

举个例子:

System.Windows.Forms.Control dest = Gimme(5);
Run Code Online (Sandbox Code Playgroud)

编译器如何知道实际控制的是什么?您需要像这样指定它:

System.Windows.Forms.Control dest = Gimme<int, System.Windows.Forms.Button>(5);
Run Code Online (Sandbox Code Playgroud)

  • 我认为问题是"为什么编译器不能推断返回类型?" 而不是"我怎样才能明确声明返回类型?" (3认同)
  • 我认为更大的问题是赋值的左侧从未指定C#中的确切类型 - 是的,它们*可能*已经做出设计决定从语言的左侧推断类型,但一般来说它因为System.Windows.Forms.Control的类结构随着时间的推移而发展,所以不会想象这可能会变得多毛. (3认同)

Jer*_*ing 5

调用Gimme(5)忽略返回值是一个法律声明,编译器将如何知道返回哪种类型?

  • @Paddy,Jeff所说的是编译器可以轻松地选择在上面失败,同时在其他情况下仍然推断返回类型.请记住,问题是编译器行为而不是运行时行为. (3认同)
  • 这不是我们在这些情况下不会试图推断*的原因吗?为什么在我们*指定返回值的类型的情况下,这会阻止我们尝试推断它? (2认同)

nob*_*ody 5

当我需要做类似的事情时我会使用这种技术:

static void Gimme<T>(out T myVariable)
{
    myVariable = default(T);
}
Run Code Online (Sandbox Code Playgroud)

并像这样使用它:

Gimme(out int myVariable);
Print(myVariable); //myVariable is already declared and usable.
Run Code Online (Sandbox Code Playgroud)

请注意,自 C# 7.0 起,可以使用 out 变量的内联声明