为什么 C # 不能从这个看似简单明了的例子中推断出类型呢

根据这个代码:

class C
{
C()
{
Test<string>(A); // fine
Test((string a) => {}); // fine
Test((Action<string>)A); // fine


Test(A); // type arguments cannot be inferred from usage!
}


static void Test<T>(Action<T> a) { }


void A(string _) { }
}

编译器抱怨 Test(A)无法将 T算成 string

对我来说,这似乎是一个非常简单的情况,我发誓我已经在其他通用实用程序和扩展函数中使用了更复杂的推理。我错过了什么?

更新1: 这在 C # 4.0编译器中。我在 VS2010中发现了这个问题,上面的示例来自我在 LINQPad 4中制作的一个最简单的复制品。

更新2: 添加了一些更多的示例到工作列表中。

3947 次浏览

我认为这是因为它是一个两步推理:

  • 它必须推断您要将 A 转换为泛型委托

  • 它必须推断委托参数的类型

我不确定这是否是原因,但我的直觉是两步推理对编译器来说并不一定容易。


编辑:

只是直觉,但我有种感觉第一步就是问题所在。编译器必须设法转换为委托 使用不同数量的通用参数,因此它不能推断参数的类型。

您正在传递 方法A 的名称。那个。Net 框架可以将其转换为 Action,但是它是隐式的,不会对此承担责任。

但是,方法名仍然是 没有,是一个显式的 Action<>对象。因此它不会推断类型为 Action类型。

在我看来这就是个恶性循环。

Test方法需要从泛型类型 Action<T>构造的委托类型的参数。而是传入一个 方法组: Test(A)。这意味着编译器必须将参数转换为委托类型(方法组转换方法组转换)。

但是哪种委托类型呢?要知道委托类型,我们需要知道 T。我们没有显式地指定它,因此编译器必须推断它来指定委托类型。

要推断方法的类型参数,我们需要知道方法参数的类型,在本例中是委托类型。编译器不知道参数类型,因此失败。

在所有其他情况下,这两种论点都是显而易见的:

// delegate is created out of anonymous method,
// no method group conversion needed - compiler knows it's Action<string>
Test((string a) => {});


// type of argument is set explicitly
Test((Action<string>)A);

或类型参数显式指定:

Test<string>(A); // compiler knows what type of delegate to convert A to

附注 更多关于类型推断的资料

Test(A);

这将失败,因为唯一适用的方法(Test<T>(Action<T>))需要类型推断,而类型推断算法要求每个参数都是某种类型或者是一个匿名函数。(这个事实是从类型推断算法的规范中推断出来的(7.5.2))方法组 A不属于任何类型(即使它可以转换为适当的委托类型) ,而且它不是一个匿名函数。

Test<string>(A);

这成功了,区别在于绑定 Test 不需要类型推断,方法组 A 可转换为所需的委托参数类型 void Action<string>(string)

Test((string a) => {});

这成功了,不同之处在于类型推断算法在第一阶段(7.5.2.1)为匿名函数做了准备。匿名函数的参数和返回类型是已知的,因此可以进行显式的参数类型推断,从而在匿名函数(void ?(string))中的类型和 Test方法参数(void Action<T>(T))的委托类型中的类型参数之间进行对应。没有为与此匿名函数算法对应的方法组指定算法。

Test((Action<string>)A);

这成功了,不同之处在于非类型化方法组参数 A被强制转换为类型,从而允许 Test的类型推断正常进行,将特定类型的表达式作为该方法的唯一参数。

我想不出理论上为什么不能在方法组 A上尝试重载解析。然后ーー如果找到一个单一的最佳绑定ーー方法组可以得到与匿名函数相同的处理。在这种情况下尤其如此,方法组只包含一个候选项,而且没有类型参数。但是它在 C # 4中无法工作的原因似乎是这个特性没有被设计和实现。考虑到这个特性的复杂性、应用程序的狭窄性以及三种简单的变通方法的存在,我不会对它屏住呼吸!

我可能错了,但我想 C # 无法推断类型的真正原因是由于方法重载和出现的模糊性。例如,假设我有以下方法: void foo (int)void foo (float)。现在如果我写 var f = foo。编译器应该选择哪个 foo?同样,使用 Test(foo)的示例也会出现同样的问题。